Fossil SCM

Merged in trunk for latest (and conflicting) /chat changes.

stephan 2021-10-04 19:48 markdown-tagrefs merge
Commit 7cae4c09813bacc866c954844f2c137042ce0d4e007191319b489793d1687ab8
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1155,12 +1155,12 @@
11551155
}
11561156
11571157
body.chat div.header, body.chat div.footer,
11581158
body.chat div.mainmenu, body.chat div.submenu,
11591159
body.chat div.content {
1160
- margin-left: auto;
1161
- margin-right: auto;
1160
+ margin-left: 0.5em;
1161
+ margin-right: 0.5em;
11621162
margin-top: auto/*eliminates unnecessary scrollbars*/;
11631163
}
11641164
body.chat.chat-only-mode div.content {
11651165
max-width: revert;
11661166
}
11671167
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1155,12 +1155,12 @@
1155 }
1156
1157 body.chat div.header, body.chat div.footer,
1158 body.chat div.mainmenu, body.chat div.submenu,
1159 body.chat div.content {
1160 margin-left: auto;
1161 margin-right: auto;
1162 margin-top: auto/*eliminates unnecessary scrollbars*/;
1163 }
1164 body.chat.chat-only-mode div.content {
1165 max-width: revert;
1166 }
1167
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1155,12 +1155,12 @@
1155 }
1156
1157 body.chat div.header, body.chat div.footer,
1158 body.chat div.mainmenu, body.chat div.submenu,
1159 body.chat div.content {
1160 margin-left: 0.5em;
1161 margin-right: 0.5em;
1162 margin-top: auto/*eliminates unnecessary scrollbars*/;
1163 }
1164 body.chat.chat-only-mode div.content {
1165 max-width: revert;
1166 }
1167
+32 -23
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142142
** send new chat message, delete older messages, or poll for changes.
143143
*/
144144
void chat_webpage(void){
145145
char *zAlert;
146146
char *zProjectName;
147
- const char * zInputPlaceholder1 = /* Placeholder for 1-line input */
148
- "Enter sends and Shift-Enter previews.";
149
- const char * zInputPlaceholder2 = /* Placeholder for textarea input*/
150
- "Ctrl-Enter sends and Shift-Enter previews.";
151147
char * zInputPlaceholder0; /* Common text input placeholder value */
148
+ const char *zPaperclip =
149
+ "<svg height=\"8.0\" width=\"16.0\"><path "
150
+ "stroke=\"rgb(100,100,100)\" "
151
+ "d=\"M 15.93452,3.2530441 "
152
+ "A 4.1499493,4.1265346 0 0 0 11.804809,6.5256284e-4 H 2.8582923 A "
153
+ "2.8239899,2.8080565 0 0 0 0.68965668,0.96142476 2.874599,2.8583801 "
154
+ "0 0 0 0.03119302,3.2388108 2.7632589,2.7476682 0 0 0 "
155
+ "0.81132923,4.7689293 3.168132,3.1502569 0 0 0 3.0300653,5.66565 l "
156
+ "7.7297897,-4e-7 a 1.6802234,1.6707433 0 0 0 0.0072,-3.3377933 H "
157
+ "5.6138192 v 1.0105899 l 5.1460358,-0.00712 a 0.66804062,0.66427143 "
158
+ "0 0 1 0,1.3237305 l -7.7226325,0.00712 A 2.0243655,2.0129437 0 0 1 "
159
+ "1.0332029,3.0964741 1.8522944,1.8418435 0 0 1 2.8511351,1.0041257 h "
160
+ "8.9465169 a 3.1478884,3.1301275 0 0 1 3.134859,2.4339559 3.0365483,"
161
+ "3.0194156 0 0 1 -0.629835,2.4908908 3.0365483,3.0194156 0 0 1 "
162
+ "-2.31178,1.0746415 l -7.5437026,-0.014233 -0.00716,1.0034736 "
163
+ "7.5365456,0.00715 a 4.048731,4.0258875 0 0 0 3.957938,-4.7469259 z\""
164
+ "/></svg>";
165
+
152166
login_check_credentials();
153167
if( !g.perm.Chat ){
154168
login_needed(g.anon.Chat);
155169
return;
156170
}
@@ -161,32 +175,27 @@
161175
mprintf("Type markdown-formatted message for %h.", zProjectName);
162176
style_set_current_feature("chat");
163177
style_header("Chat");
164178
@ <div id='chat-input-area'>
165179
@ <div id='chat-input-line' class='single-line'>
166
- @ <input type="text" name="msg" id="chat-input-single" \
167
- @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder1)" \
168
- @ autocomplete="off">
169
- @ <textarea rows="8" id="chat-input-multi" \
170
- @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
171
- @ class="hidden"></textarea>
172
- @ <div id='chat-edit-buttons'>
173
- @ <button id="chat-preview-button" \
174
- @ title="Preview message (Shift-Enter)">&#128065;</button>
175
- @ <button id="chat-settings-button" \
176
- @ title="Configure chat">&#9881;</button>
177
- @ <button id="chat-message-submit" \
178
- @ title="Send message (Ctrl-Enter)">&#128228;</button>
180
+ @ <div contenteditable id="chat-input-field" \
181
+ @ data-placeholder0="%h(zInputPlaceholder0)" \
182
+ @ data-placeholder="%h(zInputPlaceholder0)" \
183
+ @ class=""></div>
184
+ @ <div id='chat-buttons-wrapper'>
185
+ @ <span class='cbutton' id="chat-button-preview" \
186
+ @ title="Preview message (Shift-Enter)">&#128065;</span>
187
+ @ <span class='cbutton' id="chat-button-attach" \
188
+ @ title="Attach file to message">%s(zPaperclip)</span>
189
+ @ <span class='cbutton' id="chat-button-settings" \
190
+ @ title="Configure chat">&#9881;</span>
191
+ @ <span class='cbutton' id="chat-button-submit" \
192
+ @ title="Send message (Ctrl-Enter)">&#128228;</span>
179193
@ </div>
180194
@ </div>
181195
@ <div id='chat-input-file-area'>
182
- @ <div class='file-selection-wrapper'>
183
- @ <div class='help-buttonlet'>
184
- @ Select a file to upload, drag/drop a file into this spot,
185
- @ or paste an image from the clipboard if supported by
186
- @ your environment.
187
- @ </div>
196
+ @ <div class='file-selection-wrapper hidden'>
188197
@ <input type="file" name="file" id="chat-input-file">
189198
@ </div>
190199
@ <div id="chat-drop-details"></div>
191200
@ </div>
192201
@ </div>
193202
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142 ** send new chat message, delete older messages, or poll for changes.
143 */
144 void chat_webpage(void){
145 char *zAlert;
146 char *zProjectName;
147 const char * zInputPlaceholder1 = /* Placeholder for 1-line input */
148 "Enter sends and Shift-Enter previews.";
149 const char * zInputPlaceholder2 = /* Placeholder for textarea input*/
150 "Ctrl-Enter sends and Shift-Enter previews.";
151 char * zInputPlaceholder0; /* Common text input placeholder value */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152 login_check_credentials();
153 if( !g.perm.Chat ){
154 login_needed(g.anon.Chat);
155 return;
156 }
@@ -161,32 +175,27 @@
161 mprintf("Type markdown-formatted message for %h.", zProjectName);
162 style_set_current_feature("chat");
163 style_header("Chat");
164 @ <div id='chat-input-area'>
165 @ <div id='chat-input-line' class='single-line'>
166 @ <input type="text" name="msg" id="chat-input-single" \
167 @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder1)" \
168 @ autocomplete="off">
169 @ <textarea rows="8" id="chat-input-multi" \
170 @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
171 @ class="hidden"></textarea>
172 @ <div id='chat-edit-buttons'>
173 @ <button id="chat-preview-button" \
174 @ title="Preview message (Shift-Enter)">&#128065;</button>
175 @ <button id="chat-settings-button" \
176 @ title="Configure chat">&#9881;</button>
177 @ <button id="chat-message-submit" \
178 @ title="Send message (Ctrl-Enter)">&#128228;</button>
179 @ </div>
180 @ </div>
181 @ <div id='chat-input-file-area'>
182 @ <div class='file-selection-wrapper'>
183 @ <div class='help-buttonlet'>
184 @ Select a file to upload, drag/drop a file into this spot,
185 @ or paste an image from the clipboard if supported by
186 @ your environment.
187 @ </div>
188 @ <input type="file" name="file" id="chat-input-file">
189 @ </div>
190 @ <div id="chat-drop-details"></div>
191 @ </div>
192 @ </div>
193
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142 ** send new chat message, delete older messages, or poll for changes.
143 */
144 void chat_webpage(void){
145 char *zAlert;
146 char *zProjectName;
 
 
 
 
147 char * zInputPlaceholder0; /* Common text input placeholder value */
148 const char *zPaperclip =
149 "<svg height=\"8.0\" width=\"16.0\"><path "
150 "stroke=\"rgb(100,100,100)\" "
151 "d=\"M 15.93452,3.2530441 "
152 "A 4.1499493,4.1265346 0 0 0 11.804809,6.5256284e-4 H 2.8582923 A "
153 "2.8239899,2.8080565 0 0 0 0.68965668,0.96142476 2.874599,2.8583801 "
154 "0 0 0 0.03119302,3.2388108 2.7632589,2.7476682 0 0 0 "
155 "0.81132923,4.7689293 3.168132,3.1502569 0 0 0 3.0300653,5.66565 l "
156 "7.7297897,-4e-7 a 1.6802234,1.6707433 0 0 0 0.0072,-3.3377933 H "
157 "5.6138192 v 1.0105899 l 5.1460358,-0.00712 a 0.66804062,0.66427143 "
158 "0 0 1 0,1.3237305 l -7.7226325,0.00712 A 2.0243655,2.0129437 0 0 1 "
159 "1.0332029,3.0964741 1.8522944,1.8418435 0 0 1 2.8511351,1.0041257 h "
160 "8.9465169 a 3.1478884,3.1301275 0 0 1 3.134859,2.4339559 3.0365483,"
161 "3.0194156 0 0 1 -0.629835,2.4908908 3.0365483,3.0194156 0 0 1 "
162 "-2.31178,1.0746415 l -7.5437026,-0.014233 -0.00716,1.0034736 "
163 "7.5365456,0.00715 a 4.048731,4.0258875 0 0 0 3.957938,-4.7469259 z\""
164 "/></svg>";
165
166 login_check_credentials();
167 if( !g.perm.Chat ){
168 login_needed(g.anon.Chat);
169 return;
170 }
@@ -161,32 +175,27 @@
175 mprintf("Type markdown-formatted message for %h.", zProjectName);
176 style_set_current_feature("chat");
177 style_header("Chat");
178 @ <div id='chat-input-area'>
179 @ <div id='chat-input-line' class='single-line'>
180 @ <div contenteditable id="chat-input-field" \
181 @ data-placeholder0="%h(zInputPlaceholder0)" \
182 @ data-placeholder="%h(zInputPlaceholder0)" \
183 @ class=""></div>
184 @ <div id='chat-buttons-wrapper'>
185 @ <span class='cbutton' id="chat-button-preview" \
186 @ title="Preview message (Shift-Enter)">&#128065;</span>
187 @ <span class='cbutton' id="chat-button-attach" \
188 @ title="Attach file to message">%s(zPaperclip)</span>
189 @ <span class='cbutton' id="chat-button-settings" \
190 @ title="Configure chat">&#9881;</span>
191 @ <span class='cbutton' id="chat-button-submit" \
192 @ title="Send message (Ctrl-Enter)">&#128228;</span>
193 @ </div>
194 @ </div>
195 @ <div id='chat-input-file-area'>
196 @ <div class='file-selection-wrapper hidden'>
 
 
 
 
 
197 @ <input type="file" name="file" id="chat-input-file">
198 @ </div>
199 @ <div id="chat-drop-details"></div>
200 @ </div>
201 @ </div>
202
+32 -23
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142142
** send new chat message, delete older messages, or poll for changes.
143143
*/
144144
void chat_webpage(void){
145145
char *zAlert;
146146
char *zProjectName;
147
- const char * zInputPlaceholder1 = /* Placeholder for 1-line input */
148
- "Enter sends and Shift-Enter previews.";
149
- const char * zInputPlaceholder2 = /* Placeholder for textarea input*/
150
- "Ctrl-Enter sends and Shift-Enter previews.";
151147
char * zInputPlaceholder0; /* Common text input placeholder value */
148
+ const char *zPaperclip =
149
+ "<svg height=\"8.0\" width=\"16.0\"><path "
150
+ "stroke=\"rgb(100,100,100)\" "
151
+ "d=\"M 15.93452,3.2530441 "
152
+ "A 4.1499493,4.1265346 0 0 0 11.804809,6.5256284e-4 H 2.8582923 A "
153
+ "2.8239899,2.8080565 0 0 0 0.68965668,0.96142476 2.874599,2.8583801 "
154
+ "0 0 0 0.03119302,3.2388108 2.7632589,2.7476682 0 0 0 "
155
+ "0.81132923,4.7689293 3.168132,3.1502569 0 0 0 3.0300653,5.66565 l "
156
+ "7.7297897,-4e-7 a 1.6802234,1.6707433 0 0 0 0.0072,-3.3377933 H "
157
+ "5.6138192 v 1.0105899 l 5.1460358,-0.00712 a 0.66804062,0.66427143 "
158
+ "0 0 1 0,1.3237305 l -7.7226325,0.00712 A 2.0243655,2.0129437 0 0 1 "
159
+ "1.0332029,3.0964741 1.8522944,1.8418435 0 0 1 2.8511351,1.0041257 h "
160
+ "8.9465169 a 3.1478884,3.1301275 0 0 1 3.134859,2.4339559 3.0365483,"
161
+ "3.0194156 0 0 1 -0.629835,2.4908908 3.0365483,3.0194156 0 0 1 "
162
+ "-2.31178,1.0746415 l -7.5437026,-0.014233 -0.00716,1.0034736 "
163
+ "7.5365456,0.00715 a 4.048731,4.0258875 0 0 0 3.957938,-4.7469259 z\""
164
+ "/></svg>";
165
+
152166
login_check_credentials();
153167
if( !g.perm.Chat ){
154168
login_needed(g.anon.Chat);
155169
return;
156170
}
@@ -161,32 +175,27 @@
161175
mprintf("Type markdown-formatted message for %h.", zProjectName);
162176
style_set_current_feature("chat");
163177
style_header("Chat");
164178
@ <div id='chat-input-area'>
165179
@ <div id='chat-input-line' class='single-line'>
166
- @ <input type="text" name="msg" id="chat-input-single" \
167
- @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder1)" \
168
- @ autocomplete="off">
169
- @ <textarea rows="8" id="chat-input-multi" \
170
- @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
171
- @ class="hidden"></textarea>
172
- @ <div id='chat-edit-buttons'>
173
- @ <button id="chat-preview-button" \
174
- @ title="Preview message (Shift-Enter)">&#128065;</button>
175
- @ <button id="chat-settings-button" \
176
- @ title="Configure chat">&#9881;</button>
177
- @ <button id="chat-message-submit" \
178
- @ title="Send message (Ctrl-Enter)">&#128228;</button>
180
+ @ <div contenteditable id="chat-input-field" \
181
+ @ data-placeholder0="%h(zInputPlaceholder0)" \
182
+ @ data-placeholder="%h(zInputPlaceholder0)" \
183
+ @ class=""></div>
184
+ @ <div id='chat-buttons-wrapper'>
185
+ @ <span class='cbutton' id="chat-button-preview" \
186
+ @ title="Preview message (Shift-Enter)">&#128065;</span>
187
+ @ <span class='cbutton' id="chat-button-attach" \
188
+ @ title="Attach file to message">%s(zPaperclip)</span>
189
+ @ <span class='cbutton' id="chat-button-settings" \
190
+ @ title="Configure chat">&#9881;</span>
191
+ @ <span class='cbutton' id="chat-button-submit" \
192
+ @ title="Send message (Ctrl-Enter)">&#128228;</span>
179193
@ </div>
180194
@ </div>
181195
@ <div id='chat-input-file-area'>
182
- @ <div class='file-selection-wrapper'>
183
- @ <div class='help-buttonlet'>
184
- @ Select a file to upload, drag/drop a file into this spot,
185
- @ or paste an image from the clipboard if supported by
186
- @ your environment.
187
- @ </div>
196
+ @ <div class='file-selection-wrapper hidden'>
188197
@ <input type="file" name="file" id="chat-input-file">
189198
@ </div>
190199
@ <div id="chat-drop-details"></div>
191200
@ </div>
192201
@ </div>
193202
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142 ** send new chat message, delete older messages, or poll for changes.
143 */
144 void chat_webpage(void){
145 char *zAlert;
146 char *zProjectName;
147 const char * zInputPlaceholder1 = /* Placeholder for 1-line input */
148 "Enter sends and Shift-Enter previews.";
149 const char * zInputPlaceholder2 = /* Placeholder for textarea input*/
150 "Ctrl-Enter sends and Shift-Enter previews.";
151 char * zInputPlaceholder0; /* Common text input placeholder value */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152 login_check_credentials();
153 if( !g.perm.Chat ){
154 login_needed(g.anon.Chat);
155 return;
156 }
@@ -161,32 +175,27 @@
161 mprintf("Type markdown-formatted message for %h.", zProjectName);
162 style_set_current_feature("chat");
163 style_header("Chat");
164 @ <div id='chat-input-area'>
165 @ <div id='chat-input-line' class='single-line'>
166 @ <input type="text" name="msg" id="chat-input-single" \
167 @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder1)" \
168 @ autocomplete="off">
169 @ <textarea rows="8" id="chat-input-multi" \
170 @ placeholder="%h(zInputPlaceholder0) %h(zInputPlaceholder2)" \
171 @ class="hidden"></textarea>
172 @ <div id='chat-edit-buttons'>
173 @ <button id="chat-preview-button" \
174 @ title="Preview message (Shift-Enter)">&#128065;</button>
175 @ <button id="chat-settings-button" \
176 @ title="Configure chat">&#9881;</button>
177 @ <button id="chat-message-submit" \
178 @ title="Send message (Ctrl-Enter)">&#128228;</button>
179 @ </div>
180 @ </div>
181 @ <div id='chat-input-file-area'>
182 @ <div class='file-selection-wrapper'>
183 @ <div class='help-buttonlet'>
184 @ Select a file to upload, drag/drop a file into this spot,
185 @ or paste an image from the clipboard if supported by
186 @ your environment.
187 @ </div>
188 @ <input type="file" name="file" id="chat-input-file">
189 @ </div>
190 @ <div id="chat-drop-details"></div>
191 @ </div>
192 @ </div>
193
--- src/chat.c
+++ src/chat.c
@@ -142,15 +142,29 @@
142 ** send new chat message, delete older messages, or poll for changes.
143 */
144 void chat_webpage(void){
145 char *zAlert;
146 char *zProjectName;
 
 
 
 
147 char * zInputPlaceholder0; /* Common text input placeholder value */
148 const char *zPaperclip =
149 "<svg height=\"8.0\" width=\"16.0\"><path "
150 "stroke=\"rgb(100,100,100)\" "
151 "d=\"M 15.93452,3.2530441 "
152 "A 4.1499493,4.1265346 0 0 0 11.804809,6.5256284e-4 H 2.8582923 A "
153 "2.8239899,2.8080565 0 0 0 0.68965668,0.96142476 2.874599,2.8583801 "
154 "0 0 0 0.03119302,3.2388108 2.7632589,2.7476682 0 0 0 "
155 "0.81132923,4.7689293 3.168132,3.1502569 0 0 0 3.0300653,5.66565 l "
156 "7.7297897,-4e-7 a 1.6802234,1.6707433 0 0 0 0.0072,-3.3377933 H "
157 "5.6138192 v 1.0105899 l 5.1460358,-0.00712 a 0.66804062,0.66427143 "
158 "0 0 1 0,1.3237305 l -7.7226325,0.00712 A 2.0243655,2.0129437 0 0 1 "
159 "1.0332029,3.0964741 1.8522944,1.8418435 0 0 1 2.8511351,1.0041257 h "
160 "8.9465169 a 3.1478884,3.1301275 0 0 1 3.134859,2.4339559 3.0365483,"
161 "3.0194156 0 0 1 -0.629835,2.4908908 3.0365483,3.0194156 0 0 1 "
162 "-2.31178,1.0746415 l -7.5437026,-0.014233 -0.00716,1.0034736 "
163 "7.5365456,0.00715 a 4.048731,4.0258875 0 0 0 3.957938,-4.7469259 z\""
164 "/></svg>";
165
166 login_check_credentials();
167 if( !g.perm.Chat ){
168 login_needed(g.anon.Chat);
169 return;
170 }
@@ -161,32 +175,27 @@
175 mprintf("Type markdown-formatted message for %h.", zProjectName);
176 style_set_current_feature("chat");
177 style_header("Chat");
178 @ <div id='chat-input-area'>
179 @ <div id='chat-input-line' class='single-line'>
180 @ <div contenteditable id="chat-input-field" \
181 @ data-placeholder0="%h(zInputPlaceholder0)" \
182 @ data-placeholder="%h(zInputPlaceholder0)" \
183 @ class=""></div>
184 @ <div id='chat-buttons-wrapper'>
185 @ <span class='cbutton' id="chat-button-preview" \
186 @ title="Preview message (Shift-Enter)">&#128065;</span>
187 @ <span class='cbutton' id="chat-button-attach" \
188 @ title="Attach file to message">%s(zPaperclip)</span>
189 @ <span class='cbutton' id="chat-button-settings" \
190 @ title="Configure chat">&#9881;</span>
191 @ <span class='cbutton' id="chat-button-submit" \
192 @ title="Send message (Ctrl-Enter)">&#128228;</span>
193 @ </div>
194 @ </div>
195 @ <div id='chat-input-file-area'>
196 @ <div class='file-selection-wrapper hidden'>
 
 
 
 
 
197 @ <input type="file" name="file" id="chat-input-file">
198 @ </div>
199 @ <div id="chat-drop-details"></div>
200 @ </div>
201 @ </div>
202
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -188,12 +188,12 @@
188188
btnUp = this.createButton(this.FetchType.NextUp);
189189
}
190190
}
191191
//this.e.btnUp = btnUp;
192192
//this.e.btnDown = btnDown;
193
- if(btnDown) D.append(this.e.btnWrapper, btnDown);
194193
if(btnUp) D.append(this.e.btnWrapper, btnUp);
194
+ if(btnDown) D.append(this.e.btnWrapper, btnDown);
195195
D.append(this.e.btnWrapper, this.e.msgWidget);
196196
/* For debugging only... */
197197
this.e.posState = D.span();
198198
D.append(this.e.btnWrapper, this.e.posState);
199199
this.updatePosDebug();
200200
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -188,12 +188,12 @@
188 btnUp = this.createButton(this.FetchType.NextUp);
189 }
190 }
191 //this.e.btnUp = btnUp;
192 //this.e.btnDown = btnDown;
193 if(btnDown) D.append(this.e.btnWrapper, btnDown);
194 if(btnUp) D.append(this.e.btnWrapper, btnUp);
 
195 D.append(this.e.btnWrapper, this.e.msgWidget);
196 /* For debugging only... */
197 this.e.posState = D.span();
198 D.append(this.e.btnWrapper, this.e.posState);
199 this.updatePosDebug();
200
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -188,12 +188,12 @@
188 btnUp = this.createButton(this.FetchType.NextUp);
189 }
190 }
191 //this.e.btnUp = btnUp;
192 //this.e.btnDown = btnDown;
 
193 if(btnUp) D.append(this.e.btnWrapper, btnUp);
194 if(btnDown) D.append(this.e.btnWrapper, btnDown);
195 D.append(this.e.btnWrapper, this.e.msgWidget);
196 /* For debugging only... */
197 this.e.posState = D.span();
198 D.append(this.e.btnWrapper, this.e.posState);
199 this.updatePosDebug();
200
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125125
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126126
inputWrapper: E1("#chat-input-area"),
127127
inputLine: E1('#chat-input-line'),
128128
fileSelectWrapper: E1('#chat-input-file-area'),
129129
viewMessages: E1('#chat-messages-wrapper'),
130
- btnSubmit: E1('#chat-message-submit'),
131
- inputSingle: E1('#chat-input-single'),
132
- inputMulti: E1('#chat-input-multi'),
133
- inputCurrent: undefined/*one of inputSingle or inputMulti*/,
130
+ btnSubmit: E1('#chat-button-submit'),
131
+ btnAttach: E1('#chat-button-attach'),
132
+ inputField: E1('#chat-input-field'),
134133
inputFile: E1('#chat-input-file'),
135134
contentDiv: E1('div.content'),
136135
viewConfig: E1('#chat-config'),
137136
viewPreview: E1('#chat-preview'),
138137
previewContent: E1('#chat-preview-content'),
139
- btnPreview: E1('#chat-preview-button'),
138
+ btnPreview: E1('#chat-button-preview'),
140139
views: document.querySelectorAll('.chat-view'),
141140
activeUserListWrapper: E1('#chat-user-list-wrapper'),
142141
activeUserList: E1('#chat-user-list'),
143142
btnClearFilter: E1('#chat-clear-filter')
144143
},
@@ -185,56 +184,23 @@
185184
taking into account single- vs multi-line input. The getter returns
186185
a string and the setter returns this object. */
187186
inputValue: function(){
188187
const e = this.inputElement();
189188
if(arguments.length){
190
- e.value = arguments[0];
189
+ e.innerText = arguments[0];
191190
return this;
192191
}
193
- return e.value;
192
+ return e.innerText;
194193
},
195194
/** Asks the current user input field to take focus. Returns this. */
196195
inputFocus: function(){
197196
this.inputElement().focus();
198197
return this;
199198
},
200199
/** Returns the current message input element. */
201200
inputElement: function(){
202
- return this.e.inputCurrent;
203
- },
204
- /** Toggles between single- and multi-line edit modes. Returns this. */
205
- inputToggleSingleMulti: function(){
206
- const old = this.e.inputCurrent;
207
- if(this.e.inputCurrent === this.e.inputSingle){
208
- this.e.inputCurrent = this.e.inputMulti;
209
- this.e.inputLine.classList.remove('single-line');
210
- }else{
211
- this.e.inputCurrent = this.e.inputSingle;
212
- this.e.inputLine.classList.add('single-line');
213
- }
214
- const m = this.e.viewMessages,
215
- sTop = m.scrollTop,
216
- mh1 = m.clientHeight;
217
- D.addClass(old, 'hidden');
218
- D.removeClass(this.e.inputCurrent, 'hidden');
219
- const mh2 = m.clientHeight;
220
- m.scrollTo(0, sTop + (mh1-mh2));
221
- this.e.inputCurrent.value = old.value;
222
- old.value = '';
223
- return this;
224
- },
225
- /**
226
- If passed true or no arguments, switches to multi-line mode
227
- if currently in single-line mode. If passed false, switches
228
- to single-line mode if currently in multi-line mode. Returns
229
- this.
230
- */
231
- inputMultilineMode: function(yes){
232
- if(!arguments.length) yes = true;
233
- if(yes && this.e.inputCurrent === this.e.inputMulti) return this;
234
- else if(!yes && this.e.inputCurrent === this.e.inputSingle) return this;
235
- else return this.inputToggleSingleMulti();
201
+ return this.e.inputField;
236202
},
237203
/** Enables (if yes is truthy) or disables all elements in
238204
* this.disableDuringAjax. */
239205
enableAjaxComponents: function(yes){
240206
D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
409375
return e ? overlapsElemView(e, this.e.viewMessages) : false;
410376
},
411377
settings:{
412378
get: (k,dflt)=>F.storage.get(k,dflt),
413379
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
414
- set: (k,v)=>F.storage.set(k,v),
380
+ set: function(k,v){
381
+ F.storage.set(k,v);
382
+ F.page.dispatchEvent('chat-setting',{key: k, value: v});
383
+ },
415384
/* Toggles the boolean setting specified by k. Returns the
416385
new value.*/
417386
toggle: function(k){
418387
const v = this.getBool(k);
419388
this.set(k, !v);
420389
return !v;
421390
},
391
+ addListener: function(setting, f){
392
+ F.page.addEventListener('chat-setting', function(ev){
393
+ if(ev.detail.key===setting) f(ev.detail);
394
+ }, false);
395
+ },
396
+ /* Default values of settings. These are used for intializing
397
+ the setting event listeners and config view UI. */
422398
defaults:{
399
+ /* When on, inbound images are displayed inlined, else as a
400
+ link to download the image. */
423401
"images-inline": !!F.config.chat.imagesInline,
424
- "edit-multiline": false,
425
- "monospace-messages": false,
402
+ /* When on, ctrl-enter sends messages, else enter and
403
+ ctrl-enter both send them. */
404
+ "edit-ctrl-send": false,
405
+ /* When on, the edit field starts as a single line and
406
+ expands as the user types, and the relevant buttons are
407
+ laid out in a compact form. When off, the edit field and
408
+ buttons are larger. */
409
+ "edit-compact-mode": true,
410
+ /* When on, sets the font-family on messages and the edit
411
+ field to monospace. */
412
+ "monospace-messages": true,
413
+ /* When on, non-chat UI elements (page header/footer) are
414
+ hidden */
426415
"chat-only-mode": false,
416
+ /* When set to a URI, it is assumed to be an audio file,
417
+ which gets played when new messages arrive. When true,
418
+ the first entry in the audio file selection list will be
419
+ used. */
427420
"audible-alert": true,
421
+ /* When on, show the list of "active" users - those from
422
+ whom we have messages in the currently-loaded history
423
+ (noting that deletions are also messages). */
428424
"active-user-list": false,
429
- "active-user-list-timestamps": false
425
+ /* When on, the [active-user-list] setting includes the
426
+ timestamp of each user's most recent message. */
427
+ "active-user-list-timestamps": false,
428
+ /* When on, the [audible-alert] is played for one's own
429
+ messages, else it is only played for other users'
430
+ messages. */
431
+ "alert-own-messages": false
430432
}
431433
},
432434
/** Plays a new-message notification sound IF the audible-alert
433435
setting is true, else this is a no-op. Returns this.
434436
*/
@@ -437,11 +439,11 @@
437439
try{
438440
if(!f.audio) f.audio = new Audio(f.uri);
439441
f.audio.currentTime = 0;
440442
f.audio.play();
441443
}catch(e){
442
- console.error("Audio playblack failed.",e);
444
+ console.error("Audio playblack failed.", f.uri, e);
443445
}
444446
}
445447
return this;
446448
},
447449
/**
@@ -466,11 +468,12 @@
466468
return e;
467469
}
468470
this.e.views.forEach(function(E){
469471
if(e!==E) D.addClass(E,'hidden');
470472
});
471
- this.e.currentView = D.removeClass(e,'hidden');
473
+ this.e.currentView = e;
474
+ D.removeClass(e,'hidden');
472475
this.animate(this.e.currentView, 'anim-fade-in-fast');
473476
return this.e.currentView;
474477
},
475478
/**
476479
Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
585588
/*Unfortante filter-specific logic*/
586589
(e)=>e.classList.remove('selected')
587590
);
588591
return this;
589592
},
593
+ /**
594
+ Show or hide the active user list. Returns this object.
595
+ */
596
+ showActiveUserList: function(yes){
597
+ if(0===arguments.length) yes = true;
598
+ this.e.activeUserListWrapper.classList[
599
+ yes ? 'remove' : 'add'
600
+ ]('hidden');
601
+ D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
602
+ if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
603
+ /* When hiding this element, undo all filtering */
604
+ Chat.setUserFilter(false);
605
+ /*Ideally we'd scroll the final message into view
606
+ now, but because viewMessages is currently hidden behind
607
+ viewConfig, scrolling is a no-op. */
608
+ Chat.scrollMessagesTo(1);
609
+ }else{
610
+ Chat.updateActiveUserList();
611
+ Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
612
+ }
613
+ return this;
614
+ },
615
+ showActiveUserTimestamps: function(yes){
616
+ if(0===arguments.length) yes = true;
617
+ this.e.activeUserList.classList[yes ? 'add' : 'remove']('timestamps');
618
+ return this;
619
+ },
590620
/**
591621
Applies user name filter to all current messages, or clears
592622
the filter if uname is falsy.
593623
*/
594624
setUserFilter: function(uname){
@@ -634,38 +664,18 @@
634664
D.addClassBriefly(e, a, 0, cb);
635665
}
636666
return this;
637667
}
638668
};
669
+ if(!D.attr(cs.e.inputField,'contenteditable','plaintext-only').isContentEditable){
670
+ /* Only the Chrome family supports contenteditable=plaintext-only,
671
+ but Chrome is the only engine for which we need this flag: */
672
+ D.attr(cs.e.inputField,'contenteditable','true');
673
+ }
639674
cs.animate.$disabled = true;
640675
F.fetch.beforesend = ()=>cs.ajaxStart();
641676
F.fetch.aftersend = ()=>cs.ajaxEnd();
642
- cs.e.inputCurrent = cs.e.inputSingle;
643
- /* Install default settings... */
644
- Object.keys(cs.settings.defaults).forEach(function(k){
645
- const v = cs.settings.get(k,cs);
646
- if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
647
- });
648
- if(window.innerWidth<window.innerHeight){
649
- /* Alignment of 'my' messages: right alignment is conventional
650
- for mobile chat apps but can be difficult to read in wide
651
- windows (desktop/tablet landscape mode), so we default to a
652
- layout based on the apparent "orientation" of the window:
653
- tall vs wide. Can be toggled via settings popup. */
654
- document.body.classList.add('my-messages-right');
655
- }
656
- if(cs.settings.getBool('monospace-messages',false)){
657
- document.body.classList.add('monospace-messages');
658
- }
659
- if(cs.settings.getBool('active-user-list',false)){
660
- cs.e.activeUserListWrapper.classList.remove('hidden');
661
- }
662
- if(cs.settings.getBool('active-user-list-timestamps',false)){
663
- cs.e.activeUserList.classList.add('timestamps');
664
- }
665
- cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
666
- cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
667677
cs.pageTitleOrig = cs.e.pageTitle.innerText;
668678
const qs = (e)=>document.querySelector(e);
669679
const argsToArray = function(args){
670680
return Array.prototype.slice.call(args,0);
671681
};
@@ -1234,11 +1244,11 @@
12341244
}
12351245
};
12361246
return cf;
12371247
})()/*MessageWidget*/;
12381248
1239
- const BlobXferState = (function(){/*drag/drop bits...*/
1249
+ const BlobXferState = (function(){
12401250
/* State for paste and drag/drop */
12411251
const bxs = {
12421252
dropDetails: document.querySelector('#chat-drop-details'),
12431253
blob: undefined,
12441254
clear: function(){
@@ -1249,75 +1259,83 @@
12491259
};
12501260
/** Updates the paste/drop zone with details of the pasted/dropped
12511261
data. The argument must be a Blob or Blob-like object (File) or
12521262
it can be falsy to reset/clear that state.*/
12531263
const updateDropZoneContent = function(blob){
1264
+ //console.debug("updateDropZoneContent()",blob);
12541265
const dd = bxs.dropDetails;
12551266
bxs.blob = blob;
12561267
D.clearElement(dd);
12571268
if(!blob){
12581269
Chat.e.inputFile.value = '';
12591270
return;
12601271
}
1261
- D.append(dd, "Name: ", blob.name,
1272
+ D.append(dd, "Attached: ", blob.name,
12621273
D.br(), "Size: ",blob.size);
1263
- if(blob.type && blob.type.startsWith("image/")){
1274
+ const btn = D.button("Cancel");
1275
+ D.append(dd, D.br(), btn);
1276
+ btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1277
+ if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
12641278
const img = D.img();
12651279
D.append(dd, D.br(), img);
12661280
const reader = new FileReader();
12671281
reader.onload = (e)=>img.setAttribute('src', e.target.result);
12681282
reader.readAsDataURL(blob);
12691283
}
1270
- const btn = D.button("Cancel");
1271
- D.append(dd, D.br(), btn);
1272
- btn.addEventListener('click', ()=>updateDropZoneContent(), false);
12731284
};
12741285
Chat.e.inputFile.addEventListener('change', function(ev){
12751286
updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
12761287
});
12771288
/* Handle image paste from clipboard. TODO: figure out how we can
12781289
paste non-image binary data as if it had been selected via the
12791290
file selection element. */
1280
- document.addEventListener('paste', function(event){
1291
+ const pasteListener = function(event){
12811292
const items = event.clipboardData.items,
12821293
item = items[0];
1283
- if(!item || !item.type) return;
1284
- else if('file'===item.kind){
1294
+ //console.debug("paste event",event.target,item,event);
1295
+ //console.debug("paste event item",item);
1296
+ if(item && item.type && ('file'===item.kind || 'BITMAP'===item.type)){
12851297
updateDropZoneContent(false/*clear prev state*/);
12861298
updateDropZoneContent(item.getAsFile());
1287
- }
1288
- }, false);
1289
- /* Add help button for drag/drop/paste zone */
1290
- Chat.e.inputFile.parentNode.insertBefore(
1291
- F.helpButtonlets.create(
1292
- Chat.e.fileSelectWrapper.querySelector('.help-buttonlet')
1293
- ), Chat.e.inputFile
1294
- );
1295
- ////////////////////////////////////////////////////////////
1296
- // File drag/drop visual notification.
1297
- const dropHighlight = Chat.e.inputFile /* target zone */;
1298
- const dropEvents = {
1299
- drop: function(ev){
1300
- D.removeClass(dropHighlight, 'dragover');
1301
- },
1302
- dragenter: function(ev){
1299
+ event.stopPropagation();
1300
+ event.preventDefault(true);
1301
+ return false;
1302
+ }
1303
+ /* else continue propagating */
1304
+ };
1305
+ document.addEventListener('paste', pasteListener, true);
1306
+ if(0){
1307
+ const onPastePlainText = function(ev){
1308
+ var pastedText = undefined;
1309
+ if (window.clipboardData && window.clipboardData.getData) { // IE
1310
+ pastedText = window.clipboardData.getData('Text');
1311
+ }else if (ev.clipboardData && ev.clipboardData.getData) {
1312
+ pastedText = ev.clipboardData.getData('text/plain');
1313
+ }
1314
+ ev.target.textContent += pastedText;
13031315
ev.preventDefault();
1304
- ev.dataTransfer.dropEffect = "copy";
1305
- D.addClass(dropHighlight, 'dragover');
1306
- },
1307
- dragleave: function(ev){
1308
- D.removeClass(dropHighlight, 'dragover');
1309
- },
1310
- dragend: function(ev){
1311
- D.removeClass(dropHighlight, 'dragover');
1312
- }
1316
+ return false;
1317
+ };
1318
+ Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
1319
+ }
1320
+ const noDragDropEvents = function(ev){
1321
+ /* contenteditable tries to do its own thing with dropped data,
1322
+ which is not compatible with how we use it, so... */
1323
+ ev.dataTransfer.effectAllowed = 'none';
1324
+ ev.dataTransfer.dropEffect = 'none';
1325
+ ev.preventDefault();
1326
+ ev.stopPropagation();
1327
+ return false;
13131328
};
1314
- Object.keys(dropEvents).forEach(
1315
- (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
1329
+
1330
+ ['drop','dragenter','dragleave','dragend'].forEach(
1331
+ (k)=>{
1332
+ Chat.inputElement().addEventListener(k, noDragDropEvents, false);
1333
+ }
13161334
);
13171335
return bxs;
1318
- })()/*drag/drop*/;
1336
+ })()/*drag/drop/paste*/;
13191337
13201338
const tzOffsetToString = function(off){
13211339
const hours = Math.round(off/60), min = Math.round(off % 30);
13221340
return ''+(hours + (min ? '.5' : ''));
13231341
};
@@ -1335,21 +1353,30 @@
13351353
empty, this is a no-op.
13361354
*/
13371355
Chat.submitMessage = function f(){
13381356
if(!f.spaces){
13391357
f.spaces = /\s+$/;
1358
+ f.markdownContinuation = /\\\s+$/;
13401359
}
13411360
this.setCurrentView(this.e.viewMessages);
13421361
const fd = new FormData();
13431362
var msg = this.inputValue().trim();
13441363
if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
13451364
/* Cosmetic: trim whitespace from the ends of lines to try to
13461365
keep copy/paste from terminals, especially wide ones, from
1347
- forcing a horizontal scrollbar on all clients. */
1366
+ forcing a horizontal scrollbar on all clients. This breaks
1367
+ markdown's use of blackslash-space-space for paragraph
1368
+ continuation, but *not* doing this affects all clients every
1369
+ time someone pastes in console copy/paste from an affected
1370
+ platform. We seem to have narrowed to the console pasting
1371
+ problem to users of tmux. Most consoles don't behave
1372
+ that way. */
13481373
const xmsg = msg.split('\n');
13491374
xmsg.forEach(function(line,ndx){
1350
- xmsg[ndx] = line.trimRight();
1375
+ if(!f.markdownContinuation.test(line)){
1376
+ xmsg[ndx] = line.trimRight();
1377
+ }
13511378
});
13521379
msg = xmsg.join('\n');
13531380
}
13541381
if(msg) fd.set('msg',msg);
13551382
const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
13741401
});
13751402
BlobXferState.clear();
13761403
Chat.inputValue("").inputFocus();
13771404
};
13781405
1379
- const inputWidgetKeydown = function(ev){
1380
- if(13 === ev.keyCode){
1381
- if(ev.shiftKey){
1382
- ev.preventDefault();
1383
- ev.stopPropagation();
1406
+ const inputWidgetKeydown = function f(ev){
1407
+ if(!f.$toggleCtrl){
1408
+ f.$toggleCtrl = function(currentMode){
1409
+ currentMode = !currentMode;
1410
+ Chat.settings.set('edit-ctrl-send', currentMode);
1411
+ };
1412
+ f.$toggleCompact = function(currentMode){
1413
+ currentMode = !currentMode;
1414
+ Chat.settings.set('edit-compact-mode', currentMode);
1415
+ };
1416
+ }
1417
+ if(13 !== ev.keyCode) return;
1418
+ const text = Chat.inputValue().trim();
1419
+ const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
1420
+ //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
1421
+ if(ev.shiftKey){
1422
+ const compactMode = Chat.settings.getBool('edit-compact-mode', false);
1423
+ ev.preventDefault();
1424
+ ev.stopPropagation();
1425
+ /* Shift-enter will run preview mode UNLESS preview mode is
1426
+ active AND the input field is empty, in which case it will
1427
+ switch back to message view. */
1428
+ if(Chat.e.currentView===Chat.e.viewPreview && !text){
1429
+ Chat.setCurrentView(Chat.e.viewMessages);
1430
+ }else if(!text){
1431
+ f.$toggleCompact(compactMode);
1432
+ }else{
13841433
Chat.e.btnPreview.click();
1385
- return false;
1386
- }else if((Chat.e.inputSingle===ev.target)
1387
- || (ev.ctrlKey && Chat.e.inputMulti===ev.target)){
1388
- /* ^^^ note that it is intended that both ctrl-enter and enter
1389
- work for single-line input mode. */
1390
- ev.preventDefault();
1391
- ev.stopPropagation();
1392
- Chat.submitMessage();
1393
- return false;
1394
- }
1434
+ }
1435
+ return false;
1436
+ }
1437
+ if(ev.ctrlKey && !text && !BlobXferState.blob){
1438
+ /* Ctrl-enter on empty input field(s) toggles Enter/Ctrl-enter mode */
1439
+ ev.preventDefault();
1440
+ ev.stopPropagation();
1441
+ f.$toggleCtrl(ctrlMode);
1442
+ return false;
1443
+ }
1444
+ if(!ctrlMode && ev.ctrlKey && text){
1445
+ //console.debug("!ctrlMode && ev.ctrlKey && text.");
1446
+ /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
1447
+ newline, but that is not happening, for unknown reasons
1448
+ (possibly related to this element being a conteneditable DIV
1449
+ instead of a textarea). Forcibly appending a newline do the
1450
+ input area does not work, also for unknown reasons, and would
1451
+ only be suitable when we're at the end of the input.
1452
+
1453
+ Strangely, this approach DOES work for shift-enter, but we
1454
+ need shift-enter as a hotkey for preview mode.
1455
+ */
1456
+ //return;
1457
+ // return here "should" cause newline to be added, but that doesn't work
1458
+ }
1459
+ if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey/* && ctrlMode*/)){
1460
+ /* Ship it! */
1461
+ ev.preventDefault();
1462
+ ev.stopPropagation();
1463
+ Chat.submitMessage();
1464
+ return false;
13951465
}
13961466
};
1397
- Chat.e.inputSingle
1398
- .addEventListener('keydown', inputWidgetKeydown, false);
1399
- Chat.e.inputMulti
1400
- .addEventListener('keydown', inputWidgetKeydown, false);
1467
+ Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
14011468
Chat.e.btnSubmit.addEventListener('click',(e)=>{
14021469
e.preventDefault();
14031470
Chat.submitMessage();
14041471
return false;
14051472
});
1473
+ Chat.e.btnAttach.addEventListener(
1474
+ 'click', ()=>Chat.e.inputFile.click(), false);
14061475
1407
- (function(){/*Set up #chat-settings-button */
1408
- const settingsButton = document.querySelector('#chat-settings-button');
1476
+ (function(){/*Set up #chat-button-settings and related bits */
1477
+ if(window.innerWidth<window.innerHeight){
1478
+ // Must be set up before config view is...
1479
+ /* Alignment of 'my' messages: right alignment is conventional
1480
+ for mobile chat apps but can be difficult to read in wide
1481
+ windows (desktop/tablet landscape mode), so we default to a
1482
+ layout based on the apparent "orientation" of the window:
1483
+ tall vs wide. Can be toggled via settings. */
1484
+ document.body.classList.add('my-messages-right');
1485
+ }
1486
+ const settingsButton = document.querySelector('#chat-button-settings');
14091487
const optionsMenu = E1('#chat-config-options');
14101488
const cbToggle = function(ev){
14111489
ev.preventDefault();
14121490
ev.stopPropagation();
14131491
Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
14201498
/** Internal acrobatics to allow certain settings toggles to access
14211499
related toggles. */
14221500
const namedOptions = {
14231501
activeUsers:{
14241502
label: "Show active users list",
1425
- boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1426
- persistentSetting: 'active-user-list',
1427
- callback: function(){
1428
- D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1429
- D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1430
- if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1431
- /* When hiding this element, undo user filtering */
1432
- if(Chat.filter.current === Chat.filter.user){
1433
- Chat.setUserFilter(false);
1434
- }
1435
- /*Ideally we'd scroll the final message into view
1436
- now, but because viewMessages is currently hidden behind
1437
- viewConfig, scrolling is a no-op. */
1438
- Chat.scrollMessagesTo(1);
1439
- }else{
1440
- Chat.updateActiveUserList();
1441
- Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1442
- }
1443
- }
1503
+ hint: "List users who have messages in the currently-loaded chat history.",
1504
+ boolValue: 'active-user-list'
14441505
}
14451506
};
14461507
if(1){
14471508
/* Per user request, toggle the list of users on and off if the
14481509
legend element is tapped. */
@@ -1454,65 +1515,82 @@
14541515
if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
14551516
Chat.animate(optAu.theList,'anim-flip-v');
14561517
}
14571518
}, false);
14581519
}/*namedOptions.activeUsers additional setup*/
1459
- /* Settings menu entries... Remember that they will be rendered in
1460
- reverse order and the most frequently-needed ones "should"
1461
- (arguably) be closer to the start of this list so that they
1462
- will be rendered within easier reach of the settings button. */
1463
- const settingsOps = [{
1464
- label: "Multi-line input",
1465
- boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1466
- persistentSetting: 'edit-multiline',
1467
- callback: function(){
1468
- Chat.inputToggleSingleMulti();
1469
- }
1470
- },{
1471
- label: "Left-align my posts",
1472
- boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1473
- callback: function f(){
1474
- document.body.classList.toggle('my-messages-right');
1475
- }
1476
- },{
1477
- label: "Show images inline",
1478
- boolValue: ()=>Chat.settings.getBool('images-inline'),
1479
- callback: function(){
1480
- const v = Chat.settings.toggle('images-inline');
1481
- F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1482
- }
1483
- },{
1484
- label: "Timestamps in active users list",
1485
- boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1486
- persistentSetting: 'active-user-list-timestamps',
1487
- callback: function(){
1488
- D.toggleClass(Chat.e.activeUserList,'timestamps');
1489
- /* If the timestamp option is activated but
1490
- namedOptions.activeUsers is not currently checked then
1491
- toggle that option on as well. */
1492
- if(Chat.e.activeUserList.classList.contains('timestamps')
1493
- && !namedOptions.activeUsers.boolValue()){
1494
- namedOptions.activeUsers.checkbox.checked = true;
1495
- namedOptions.activeUsers.callback();
1496
- Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1497
- }
1498
- }
1499
- },
1500
- namedOptions.activeUsers,{
1501
- label: "Monospace message font",
1502
- boolValue: ()=>document.body.classList.contains('monospace-messages'),
1503
- persistentSetting: 'monospace-messages',
1504
- callback: function(){
1505
- document.body.classList.toggle('monospace-messages');
1520
+ /**
1521
+ Settings options structure: an array of Objects with the
1522
+ following properties:
1523
+
1524
+ label: string for the UI
1525
+
1526
+ boolValue: string (name of Chat.settings setting) or a
1527
+ function which returns true or false. Gets mapped to
1528
+ a checkbox.
1529
+
1530
+ select: SELECT element (instead of boolValue)
1531
+
1532
+ callback: optional handler to call after setting is modified.
1533
+ It gets passed the setting object: {key:string, value:something}.
1534
+
1535
+ If a setting has a boolValue set, that gets transformed into a
1536
+ checkbox which toggles the given persistent setting (if
1537
+ boolValue is a string) AND listens for changes to that setting
1538
+ fired via Chat.settings.set() so that the checkbox can stay in
1539
+ sync with external changes to that setting. Various Chat UI
1540
+ elements stay in sync with the config UI via those settings
1541
+ events.
1542
+ */
1543
+ const settingsOps = [{
1544
+ label: "Ctrl-enter to Send",
1545
+ hint: [
1546
+ "When on, only Ctrl-Enter will send messages and Enter adds ",
1547
+ "blank lines. ",
1548
+ "When off, both Enter and Ctrl-Enter send. ",
1549
+ "When the input field has focus, is empty, and preview ",
1550
+ "mode is NOT active then Ctrl-Enter toggles this setting."
1551
+ ].join(''),
1552
+ boolValue: 'edit-ctrl-send'
1553
+ },{
1554
+ label: "Compact mode",
1555
+ hint: [
1556
+ "Toggle between a space-saving or more spacious writing area. ",
1557
+ "When the input field has focus, is empty, and preview mode ",
1558
+ "is NOT active then Shift-Enter toggles this setting."
1559
+ ].join(''),
1560
+ boolValue: 'edit-compact-mode'
1561
+ },{
1562
+ label: "Left-align my posts",
1563
+ hint: "Default alignment of your own messages is selected "
1564
+ +"based window width/height relationship.",
1565
+ boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1566
+ callback: function f(){
1567
+ document.body.classList[
1568
+ this.checkbox.checked ? 'remove' : 'add'
1569
+ ]('my-messages-right');
1570
+ }
1571
+ },{
1572
+ label: "Monospace message font",
1573
+ hint: "Use monospace font for message text?",
1574
+ boolValue: 'monospace-messages',
1575
+ callback: function(setting){
1576
+ document.body.classList[
1577
+ setting.value ? 'add' : 'remove'
1578
+ ]('monospace-messages');
15061579
}
15071580
},{
15081581
label: "Chat-only mode",
1509
- boolValue: ()=>Chat.isChatOnlyMode(),
1510
- persistentSetting: 'chat-only-mode',
1511
- callback: function(){
1512
- Chat.toggleChatOnlyMode();
1513
- }
1582
+ hint: "Toggle the page between normal fossil view and chat-only view.",
1583
+ boolValue: 'chat-only-mode'
1584
+ },{
1585
+ label: "Show images inline",
1586
+ hint: "Whether to show images inline or as a hyperlink.",
1587
+ boolValue: 'images-inline'
1588
+ },namedOptions.activeUsers,{
1589
+ label: "Timestamps in active users list",
1590
+ hint: "Whether to show last-message timestamps.",
1591
+ boolValue: 'active-user-list-timestamps'
15141592
}];
15151593
15161594
/** Set up selection list of notification sounds. */
15171595
if(1){
15181596
const selectSound = D.select();
@@ -1522,91 +1600,168 @@
15221600
if(true===Chat.settings.getBool('audible-alert')){
15231601
/* This setting used to be a plain bool. If we encounter
15241602
such a setting, take the first sound in the list. */
15251603
selectSound.selectedIndex = firstSoundIndex;
15261604
}else{
1527
- selectSound.value = Chat.settings.get('audible-alert','');
1605
+ selectSound.value = Chat.settings.get('audible-alert','<none>');
15281606
if(selectSound.selectedIndex<0){
15291607
/* Missing file - removed after this setting was
15301608
applied. Fall back to the first sound in the list. */
15311609
selectSound.selectedIndex = firstSoundIndex;
15321610
}
15331611
}
15341612
Chat.setNewMessageSound(selectSound.value);
15351613
settingsOps.push({
1536
- label: "Audio alert",
1614
+ hint: "Audio alert. How to enable audio playback is browser-specific!",
15371615
select: selectSound,
15381616
callback: function(ev){
15391617
const v = ev.target.value;
15401618
Chat.setNewMessageSound(v);
15411619
F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
15421620
if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
15431621
}
15441622
});
15451623
}/*audio notification config*/
1624
+ settingsOps.push({
1625
+ label: "Play notification for your own messages.",
1626
+ hint: "When enabled, the audio notification will be played for all messages, "+
1627
+ "including your own. When disabled only messages from other users "+
1628
+ "will trigger a notification.",
1629
+ boolValue: 'alert-own-messages'
1630
+ });
15461631
/**
15471632
Build UI for config options...
15481633
*/
15491634
settingsOps.forEach(function f(op){
15501635
const line = D.addClass(D.div(), 'menu-entry');
1551
- const btn = D.append(
1552
- D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1553
- op.label);
1554
- const callback = function(ev){
1555
- op.callback(ev);
1556
- if(op.persistentSetting){
1557
- Chat.settings.set(op.persistentSetting, op.boolValue());
1558
- }
1559
- };
1636
+ const label = op.label
1637
+ ? D.append(D.label(),op.label) : undefined;
1638
+ const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1639
+ var hint;
1640
+ const col0 = D.span();
1641
+ if(op.hint){
1642
+ hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1643
+ }
15601644
if(op.hasOwnProperty('select')){
1561
- D.append(line, btn, op.select);
1562
- op.select.addEventListener('change', callback, false);
1645
+ D.append(line, col0, labelWrapper);
1646
+ D.append(labelWrapper, op.select);
1647
+ if(hint) D.append(labelWrapper, hint);
1648
+ if(label) D.append(col0, label);
1649
+ if(op.callback){
1650
+ op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1651
+ }
15631652
}else if(op.hasOwnProperty('boolValue')){
15641653
if(undefined === f.$id) f.$id = 0;
15651654
++f.$id;
1655
+ if('string' ===typeof op.boolValue){
1656
+ const key = op.boolValue;
1657
+ op.boolValue = ()=>Chat.settings.getBool(key);
1658
+ op.persistentSetting = key;
1659
+ }
15661660
const check = op.checkbox
15671661
= D.attr(D.checkbox(1, op.boolValue()),
15681662
'aria-label', op.label);
15691663
const id = 'cfgopt'+f.$id;
1570
- if(op.boolValue()) check.checked = true;
1664
+ check.checked = op.boolValue();
1665
+ op.checkbox = check;
15711666
D.attr(check, 'id', id);
1572
- D.attr(btn, 'for', id);
1573
- D.append(line, check);
1574
- check.addEventListener('change', callback);
1575
- D.append(line, btn);
1667
+ D.append(line, col0, labelWrapper);
1668
+ D.append(col0, check);
1669
+ if(label){
1670
+ D.attr(label, 'for', id);
1671
+ D.append(labelWrapper, label);
1672
+ }
1673
+ if(hint) D.append(labelWrapper, hint);
15761674
}else{
15771675
line.addEventListener('click', callback);
1578
- D.append(line, btn);
1676
+ D.append(line, col0, labelWrapper);
1677
+ if(label) D.append(labelWrapper, label);
1678
+ if(hint) D.append(labelWrapper, hint);
15791679
}
15801680
D.append(optionsMenu, line);
1681
+ if(op.persistentSetting){
1682
+ Chat.settings.addListener(
1683
+ op.persistentSetting,
1684
+ function(setting){
1685
+ if(op.checkbox) op.checkbox.checked = !!setting.value;
1686
+ else if(op.select) op.select.value = setting.value;
1687
+ if(op.callback) op.callback(setting);
1688
+ }
1689
+ );
1690
+ if(op.checkbox){
1691
+ op.checkbox.addEventListener(
1692
+ 'change', function(){
1693
+ Chat.settings.set(op.persistentSetting, op.checkbox.checked)
1694
+ }, false);
1695
+ }
1696
+ }else if(op.callback && op.checkbox){
1697
+ op.checkbox.addEventListener('change', (ev)=>op.callback(ev), false);
1698
+ }
15811699
});
1582
- if(0 && settingsOps.selectSound){
1583
- D.append(optionsMenu, settingsOps.selectSound);
1584
- }
1585
- //settingsButton.click()/*for for development*/;
1586
- })()/*#chat-settings-button setup*/;
1700
+ })()/*#chat-button-settings setup*/;
15871701
1702
+ (function(){
1703
+ /* Install default settings... must come after
1704
+ chat-button-settings setup so that the listeners which that
1705
+ installs are notified via the properties getting initialized
1706
+ here. */
1707
+ Chat.settings.addListener('monospace-messages',function(s){
1708
+ document.body.classList[s.value ? 'add' : 'remove']('monospace-messages');
1709
+ })
1710
+ Chat.settings.addListener('active-user-list',function(s){
1711
+ Chat.showActiveUserList(s.value);
1712
+ });
1713
+ Chat.settings.addListener('active-user-list-timestamps',function(s){
1714
+ Chat.showActiveUserTimestamps(s.value);
1715
+ });
1716
+ Chat.settings.addListener('chat-only-mode',function(s){
1717
+ Chat.chatOnlyMode(s.value);
1718
+ });
1719
+ Chat.settings.addListener('edit-compact-mode',function(s){
1720
+ Chat.e.inputLine.classList[
1721
+ s.value ? 'add' : 'remove'
1722
+ ]('compact');
1723
+ });
1724
+ Chat.settings.addListener('edit-ctrl-send',function(s){
1725
+ const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1726
+ const eInput = Chat.inputElement();
1727
+ eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
1728
+ Chat.e.btnSubmit.title = label;
1729
+ });
1730
+ const valueKludges = {
1731
+ /* Convert certain string-format values to other types... */
1732
+ "false": false,
1733
+ "true": true
1734
+ };
1735
+ Object.keys(Chat.settings.defaults).forEach(function(k){
1736
+ var v = Chat.settings.get(k,Chat);
1737
+ if(Chat===v) v = Chat.settings.defaults[k];
1738
+ if(valueKludges.hasOwnProperty(v)) v = valueKludges[v];
1739
+ Chat.settings.set(k,v)
1740
+ /* fires event listeners so that the Config area checkboxes
1741
+ get in sync */;
1742
+ });
1743
+ })();
1744
+
15881745
(function(){/*set up message preview*/
15891746
const btnPreview = Chat.e.btnPreview;
15901747
Chat.setPreviewText = function(t){
15911748
this.setCurrentView(this.e.viewPreview);
15921749
this.e.previewContent.innerHTML = t;
15931750
this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
15941751
setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1595
- this.e.inputCurrent.focus();
1752
+ this.inputFocus();
15961753
};
15971754
Chat.e.viewPreview.querySelector('#chat-preview-close').
15981755
addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
15991756
let previewPending = false;
1600
- const elemsToEnable = [
1601
- btnPreview, Chat.e.btnSubmit,
1602
- Chat.e.inputSingle, Chat.e.inputMulti];
1757
+ const elemsToEnable = [btnPreview, Chat.e.btnSubmit, Chat.e.inputField];
16031758
const submit = function(ev){
16041759
ev.preventDefault();
16051760
ev.stopPropagation();
16061761
if(previewPending) return false;
1607
- const txt = Chat.e.inputCurrent.value;
1762
+ const txt = Chat.inputValue();
16081763
if(!txt){
16091764
Chat.setPreviewText('');
16101765
previewPending = false;
16111766
return false;
16121767
}
@@ -1665,11 +1820,13 @@
16651820
if( m.mdel ){
16661821
/* A record deletion notice. */
16671822
Chat.deleteMessageElem(m.mdel);
16681823
return;
16691824
}
1670
- if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
1825
+ if(!Chat._isBatchLoading
1826
+ && (Chat.me!==m.xfrom
1827
+ || Chat.settings.getBool('alert-own-messages'))){
16711828
Chat.playNewMessageSound();
16721829
}
16731830
const row = new Chat.MessageWidget(m);
16741831
Chat.injectMessageElem(row.e.body,atEnd);
16751832
if(m.isError){
@@ -1831,13 +1988,13 @@
18311988
Chat.chatOnlyMode(true);
18321989
}
18331990
Chat.intervalTimer = setInterval(poll, 1000);
18341991
if(0){
18351992
const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1836
- document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1993
+ document.querySelectorAll('#chat-buttons-wrapper .cbutton').forEach(function(e){
18371994
e.addEventListener('click',flip, false);
18381995
});
18391996
}
18401997
setTimeout( ()=>Chat.inputFocus(), 0 );
18411998
Chat.animate.$disabled = false;
18421999
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
18432000
});
18442001
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126 inputWrapper: E1("#chat-input-area"),
127 inputLine: E1('#chat-input-line'),
128 fileSelectWrapper: E1('#chat-input-file-area'),
129 viewMessages: E1('#chat-messages-wrapper'),
130 btnSubmit: E1('#chat-message-submit'),
131 inputSingle: E1('#chat-input-single'),
132 inputMulti: E1('#chat-input-multi'),
133 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
134 inputFile: E1('#chat-input-file'),
135 contentDiv: E1('div.content'),
136 viewConfig: E1('#chat-config'),
137 viewPreview: E1('#chat-preview'),
138 previewContent: E1('#chat-preview-content'),
139 btnPreview: E1('#chat-preview-button'),
140 views: document.querySelectorAll('.chat-view'),
141 activeUserListWrapper: E1('#chat-user-list-wrapper'),
142 activeUserList: E1('#chat-user-list'),
143 btnClearFilter: E1('#chat-clear-filter')
144 },
@@ -185,56 +184,23 @@
185 taking into account single- vs multi-line input. The getter returns
186 a string and the setter returns this object. */
187 inputValue: function(){
188 const e = this.inputElement();
189 if(arguments.length){
190 e.value = arguments[0];
191 return this;
192 }
193 return e.value;
194 },
195 /** Asks the current user input field to take focus. Returns this. */
196 inputFocus: function(){
197 this.inputElement().focus();
198 return this;
199 },
200 /** Returns the current message input element. */
201 inputElement: function(){
202 return this.e.inputCurrent;
203 },
204 /** Toggles between single- and multi-line edit modes. Returns this. */
205 inputToggleSingleMulti: function(){
206 const old = this.e.inputCurrent;
207 if(this.e.inputCurrent === this.e.inputSingle){
208 this.e.inputCurrent = this.e.inputMulti;
209 this.e.inputLine.classList.remove('single-line');
210 }else{
211 this.e.inputCurrent = this.e.inputSingle;
212 this.e.inputLine.classList.add('single-line');
213 }
214 const m = this.e.viewMessages,
215 sTop = m.scrollTop,
216 mh1 = m.clientHeight;
217 D.addClass(old, 'hidden');
218 D.removeClass(this.e.inputCurrent, 'hidden');
219 const mh2 = m.clientHeight;
220 m.scrollTo(0, sTop + (mh1-mh2));
221 this.e.inputCurrent.value = old.value;
222 old.value = '';
223 return this;
224 },
225 /**
226 If passed true or no arguments, switches to multi-line mode
227 if currently in single-line mode. If passed false, switches
228 to single-line mode if currently in multi-line mode. Returns
229 this.
230 */
231 inputMultilineMode: function(yes){
232 if(!arguments.length) yes = true;
233 if(yes && this.e.inputCurrent === this.e.inputMulti) return this;
234 else if(!yes && this.e.inputCurrent === this.e.inputSingle) return this;
235 else return this.inputToggleSingleMulti();
236 },
237 /** Enables (if yes is truthy) or disables all elements in
238 * this.disableDuringAjax. */
239 enableAjaxComponents: function(yes){
240 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
409 return e ? overlapsElemView(e, this.e.viewMessages) : false;
410 },
411 settings:{
412 get: (k,dflt)=>F.storage.get(k,dflt),
413 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
414 set: (k,v)=>F.storage.set(k,v),
 
 
 
415 /* Toggles the boolean setting specified by k. Returns the
416 new value.*/
417 toggle: function(k){
418 const v = this.getBool(k);
419 this.set(k, !v);
420 return !v;
421 },
 
 
 
 
 
 
 
422 defaults:{
 
 
423 "images-inline": !!F.config.chat.imagesInline,
424 "edit-multiline": false,
425 "monospace-messages": false,
 
 
 
 
 
 
 
 
 
 
 
426 "chat-only-mode": false,
 
 
 
 
427 "audible-alert": true,
 
 
 
428 "active-user-list": false,
429 "active-user-list-timestamps": false
 
 
 
 
 
 
430 }
431 },
432 /** Plays a new-message notification sound IF the audible-alert
433 setting is true, else this is a no-op. Returns this.
434 */
@@ -437,11 +439,11 @@
437 try{
438 if(!f.audio) f.audio = new Audio(f.uri);
439 f.audio.currentTime = 0;
440 f.audio.play();
441 }catch(e){
442 console.error("Audio playblack failed.",e);
443 }
444 }
445 return this;
446 },
447 /**
@@ -466,11 +468,12 @@
466 return e;
467 }
468 this.e.views.forEach(function(E){
469 if(e!==E) D.addClass(E,'hidden');
470 });
471 this.e.currentView = D.removeClass(e,'hidden');
 
472 this.animate(this.e.currentView, 'anim-fade-in-fast');
473 return this.e.currentView;
474 },
475 /**
476 Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
585 /*Unfortante filter-specific logic*/
586 (e)=>e.classList.remove('selected')
587 );
588 return this;
589 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590 /**
591 Applies user name filter to all current messages, or clears
592 the filter if uname is falsy.
593 */
594 setUserFilter: function(uname){
@@ -634,38 +664,18 @@
634 D.addClassBriefly(e, a, 0, cb);
635 }
636 return this;
637 }
638 };
 
 
 
 
 
639 cs.animate.$disabled = true;
640 F.fetch.beforesend = ()=>cs.ajaxStart();
641 F.fetch.aftersend = ()=>cs.ajaxEnd();
642 cs.e.inputCurrent = cs.e.inputSingle;
643 /* Install default settings... */
644 Object.keys(cs.settings.defaults).forEach(function(k){
645 const v = cs.settings.get(k,cs);
646 if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
647 });
648 if(window.innerWidth<window.innerHeight){
649 /* Alignment of 'my' messages: right alignment is conventional
650 for mobile chat apps but can be difficult to read in wide
651 windows (desktop/tablet landscape mode), so we default to a
652 layout based on the apparent "orientation" of the window:
653 tall vs wide. Can be toggled via settings popup. */
654 document.body.classList.add('my-messages-right');
655 }
656 if(cs.settings.getBool('monospace-messages',false)){
657 document.body.classList.add('monospace-messages');
658 }
659 if(cs.settings.getBool('active-user-list',false)){
660 cs.e.activeUserListWrapper.classList.remove('hidden');
661 }
662 if(cs.settings.getBool('active-user-list-timestamps',false)){
663 cs.e.activeUserList.classList.add('timestamps');
664 }
665 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
666 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
667 cs.pageTitleOrig = cs.e.pageTitle.innerText;
668 const qs = (e)=>document.querySelector(e);
669 const argsToArray = function(args){
670 return Array.prototype.slice.call(args,0);
671 };
@@ -1234,11 +1244,11 @@
1234 }
1235 };
1236 return cf;
1237 })()/*MessageWidget*/;
1238
1239 const BlobXferState = (function(){/*drag/drop bits...*/
1240 /* State for paste and drag/drop */
1241 const bxs = {
1242 dropDetails: document.querySelector('#chat-drop-details'),
1243 blob: undefined,
1244 clear: function(){
@@ -1249,75 +1259,83 @@
1249 };
1250 /** Updates the paste/drop zone with details of the pasted/dropped
1251 data. The argument must be a Blob or Blob-like object (File) or
1252 it can be falsy to reset/clear that state.*/
1253 const updateDropZoneContent = function(blob){
 
1254 const dd = bxs.dropDetails;
1255 bxs.blob = blob;
1256 D.clearElement(dd);
1257 if(!blob){
1258 Chat.e.inputFile.value = '';
1259 return;
1260 }
1261 D.append(dd, "Name: ", blob.name,
1262 D.br(), "Size: ",blob.size);
1263 if(blob.type && blob.type.startsWith("image/")){
 
 
 
1264 const img = D.img();
1265 D.append(dd, D.br(), img);
1266 const reader = new FileReader();
1267 reader.onload = (e)=>img.setAttribute('src', e.target.result);
1268 reader.readAsDataURL(blob);
1269 }
1270 const btn = D.button("Cancel");
1271 D.append(dd, D.br(), btn);
1272 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1273 };
1274 Chat.e.inputFile.addEventListener('change', function(ev){
1275 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
1276 });
1277 /* Handle image paste from clipboard. TODO: figure out how we can
1278 paste non-image binary data as if it had been selected via the
1279 file selection element. */
1280 document.addEventListener('paste', function(event){
1281 const items = event.clipboardData.items,
1282 item = items[0];
1283 if(!item || !item.type) return;
1284 else if('file'===item.kind){
 
1285 updateDropZoneContent(false/*clear prev state*/);
1286 updateDropZoneContent(item.getAsFile());
1287 }
1288 }, false);
1289 /* Add help button for drag/drop/paste zone */
1290 Chat.e.inputFile.parentNode.insertBefore(
1291 F.helpButtonlets.create(
1292 Chat.e.fileSelectWrapper.querySelector('.help-buttonlet')
1293 ), Chat.e.inputFile
1294 );
1295 ////////////////////////////////////////////////////////////
1296 // File drag/drop visual notification.
1297 const dropHighlight = Chat.e.inputFile /* target zone */;
1298 const dropEvents = {
1299 drop: function(ev){
1300 D.removeClass(dropHighlight, 'dragover');
1301 },
1302 dragenter: function(ev){
1303 ev.preventDefault();
1304 ev.dataTransfer.dropEffect = "copy";
1305 D.addClass(dropHighlight, 'dragover');
1306 },
1307 dragleave: function(ev){
1308 D.removeClass(dropHighlight, 'dragover');
1309 },
1310 dragend: function(ev){
1311 D.removeClass(dropHighlight, 'dragover');
1312 }
 
 
 
1313 };
1314 Object.keys(dropEvents).forEach(
1315 (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
 
 
 
1316 );
1317 return bxs;
1318 })()/*drag/drop*/;
1319
1320 const tzOffsetToString = function(off){
1321 const hours = Math.round(off/60), min = Math.round(off % 30);
1322 return ''+(hours + (min ? '.5' : ''));
1323 };
@@ -1335,21 +1353,30 @@
1335 empty, this is a no-op.
1336 */
1337 Chat.submitMessage = function f(){
1338 if(!f.spaces){
1339 f.spaces = /\s+$/;
 
1340 }
1341 this.setCurrentView(this.e.viewMessages);
1342 const fd = new FormData();
1343 var msg = this.inputValue().trim();
1344 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
1345 /* Cosmetic: trim whitespace from the ends of lines to try to
1346 keep copy/paste from terminals, especially wide ones, from
1347 forcing a horizontal scrollbar on all clients. */
 
 
 
 
 
 
1348 const xmsg = msg.split('\n');
1349 xmsg.forEach(function(line,ndx){
1350 xmsg[ndx] = line.trimRight();
 
 
1351 });
1352 msg = xmsg.join('\n');
1353 }
1354 if(msg) fd.set('msg',msg);
1355 const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
1374 });
1375 BlobXferState.clear();
1376 Chat.inputValue("").inputFocus();
1377 };
1378
1379 const inputWidgetKeydown = function(ev){
1380 if(13 === ev.keyCode){
1381 if(ev.shiftKey){
1382 ev.preventDefault();
1383 ev.stopPropagation();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1384 Chat.e.btnPreview.click();
1385 return false;
1386 }else if((Chat.e.inputSingle===ev.target)
1387 || (ev.ctrlKey && Chat.e.inputMulti===ev.target)){
1388 /* ^^^ note that it is intended that both ctrl-enter and enter
1389 work for single-line input mode. */
1390 ev.preventDefault();
1391 ev.stopPropagation();
1392 Chat.submitMessage();
1393 return false;
1394 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1395 }
1396 };
1397 Chat.e.inputSingle
1398 .addEventListener('keydown', inputWidgetKeydown, false);
1399 Chat.e.inputMulti
1400 .addEventListener('keydown', inputWidgetKeydown, false);
1401 Chat.e.btnSubmit.addEventListener('click',(e)=>{
1402 e.preventDefault();
1403 Chat.submitMessage();
1404 return false;
1405 });
 
 
1406
1407 (function(){/*Set up #chat-settings-button */
1408 const settingsButton = document.querySelector('#chat-settings-button');
 
 
 
 
 
 
 
 
 
1409 const optionsMenu = E1('#chat-config-options');
1410 const cbToggle = function(ev){
1411 ev.preventDefault();
1412 ev.stopPropagation();
1413 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
1420 /** Internal acrobatics to allow certain settings toggles to access
1421 related toggles. */
1422 const namedOptions = {
1423 activeUsers:{
1424 label: "Show active users list",
1425 boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1426 persistentSetting: 'active-user-list',
1427 callback: function(){
1428 D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1429 D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1430 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1431 /* When hiding this element, undo user filtering */
1432 if(Chat.filter.current === Chat.filter.user){
1433 Chat.setUserFilter(false);
1434 }
1435 /*Ideally we'd scroll the final message into view
1436 now, but because viewMessages is currently hidden behind
1437 viewConfig, scrolling is a no-op. */
1438 Chat.scrollMessagesTo(1);
1439 }else{
1440 Chat.updateActiveUserList();
1441 Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1442 }
1443 }
1444 }
1445 };
1446 if(1){
1447 /* Per user request, toggle the list of users on and off if the
1448 legend element is tapped. */
@@ -1454,65 +1515,82 @@
1454 if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1455 Chat.animate(optAu.theList,'anim-flip-v');
1456 }
1457 }, false);
1458 }/*namedOptions.activeUsers additional setup*/
1459 /* Settings menu entries... Remember that they will be rendered in
1460 reverse order and the most frequently-needed ones "should"
1461 (arguably) be closer to the start of this list so that they
1462 will be rendered within easier reach of the settings button. */
1463 const settingsOps = [{
1464 label: "Multi-line input",
1465 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1466 persistentSetting: 'edit-multiline',
1467 callback: function(){
1468 Chat.inputToggleSingleMulti();
1469 }
1470 },{
1471 label: "Left-align my posts",
1472 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1473 callback: function f(){
1474 document.body.classList.toggle('my-messages-right');
1475 }
1476 },{
1477 label: "Show images inline",
1478 boolValue: ()=>Chat.settings.getBool('images-inline'),
1479 callback: function(){
1480 const v = Chat.settings.toggle('images-inline');
1481 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1482 }
1483 },{
1484 label: "Timestamps in active users list",
1485 boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1486 persistentSetting: 'active-user-list-timestamps',
1487 callback: function(){
1488 D.toggleClass(Chat.e.activeUserList,'timestamps');
1489 /* If the timestamp option is activated but
1490 namedOptions.activeUsers is not currently checked then
1491 toggle that option on as well. */
1492 if(Chat.e.activeUserList.classList.contains('timestamps')
1493 && !namedOptions.activeUsers.boolValue()){
1494 namedOptions.activeUsers.checkbox.checked = true;
1495 namedOptions.activeUsers.callback();
1496 Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1497 }
1498 }
1499 },
1500 namedOptions.activeUsers,{
1501 label: "Monospace message font",
1502 boolValue: ()=>document.body.classList.contains('monospace-messages'),
1503 persistentSetting: 'monospace-messages',
1504 callback: function(){
1505 document.body.classList.toggle('monospace-messages');
 
 
 
 
 
 
 
 
 
 
 
 
1506 }
1507 },{
1508 label: "Chat-only mode",
1509 boolValue: ()=>Chat.isChatOnlyMode(),
1510 persistentSetting: 'chat-only-mode',
1511 callback: function(){
1512 Chat.toggleChatOnlyMode();
1513 }
 
 
 
 
 
1514 }];
1515
1516 /** Set up selection list of notification sounds. */
1517 if(1){
1518 const selectSound = D.select();
@@ -1522,91 +1600,168 @@
1522 if(true===Chat.settings.getBool('audible-alert')){
1523 /* This setting used to be a plain bool. If we encounter
1524 such a setting, take the first sound in the list. */
1525 selectSound.selectedIndex = firstSoundIndex;
1526 }else{
1527 selectSound.value = Chat.settings.get('audible-alert','');
1528 if(selectSound.selectedIndex<0){
1529 /* Missing file - removed after this setting was
1530 applied. Fall back to the first sound in the list. */
1531 selectSound.selectedIndex = firstSoundIndex;
1532 }
1533 }
1534 Chat.setNewMessageSound(selectSound.value);
1535 settingsOps.push({
1536 label: "Audio alert",
1537 select: selectSound,
1538 callback: function(ev){
1539 const v = ev.target.value;
1540 Chat.setNewMessageSound(v);
1541 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1542 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1543 }
1544 });
1545 }/*audio notification config*/
 
 
 
 
 
 
 
1546 /**
1547 Build UI for config options...
1548 */
1549 settingsOps.forEach(function f(op){
1550 const line = D.addClass(D.div(), 'menu-entry');
1551 const btn = D.append(
1552 D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1553 op.label);
1554 const callback = function(ev){
1555 op.callback(ev);
1556 if(op.persistentSetting){
1557 Chat.settings.set(op.persistentSetting, op.boolValue());
1558 }
1559 };
1560 if(op.hasOwnProperty('select')){
1561 D.append(line, btn, op.select);
1562 op.select.addEventListener('change', callback, false);
 
 
 
 
 
1563 }else if(op.hasOwnProperty('boolValue')){
1564 if(undefined === f.$id) f.$id = 0;
1565 ++f.$id;
 
 
 
 
 
1566 const check = op.checkbox
1567 = D.attr(D.checkbox(1, op.boolValue()),
1568 'aria-label', op.label);
1569 const id = 'cfgopt'+f.$id;
1570 if(op.boolValue()) check.checked = true;
 
1571 D.attr(check, 'id', id);
1572 D.attr(btn, 'for', id);
1573 D.append(line, check);
1574 check.addEventListener('change', callback);
1575 D.append(line, btn);
 
 
 
1576 }else{
1577 line.addEventListener('click', callback);
1578 D.append(line, btn);
 
 
1579 }
1580 D.append(optionsMenu, line);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1581 });
1582 if(0 && settingsOps.selectSound){
1583 D.append(optionsMenu, settingsOps.selectSound);
1584 }
1585 //settingsButton.click()/*for for development*/;
1586 })()/*#chat-settings-button setup*/;
1587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1588 (function(){/*set up message preview*/
1589 const btnPreview = Chat.e.btnPreview;
1590 Chat.setPreviewText = function(t){
1591 this.setCurrentView(this.e.viewPreview);
1592 this.e.previewContent.innerHTML = t;
1593 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1594 setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1595 this.e.inputCurrent.focus();
1596 };
1597 Chat.e.viewPreview.querySelector('#chat-preview-close').
1598 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
1599 let previewPending = false;
1600 const elemsToEnable = [
1601 btnPreview, Chat.e.btnSubmit,
1602 Chat.e.inputSingle, Chat.e.inputMulti];
1603 const submit = function(ev){
1604 ev.preventDefault();
1605 ev.stopPropagation();
1606 if(previewPending) return false;
1607 const txt = Chat.e.inputCurrent.value;
1608 if(!txt){
1609 Chat.setPreviewText('');
1610 previewPending = false;
1611 return false;
1612 }
@@ -1665,11 +1820,13 @@
1665 if( m.mdel ){
1666 /* A record deletion notice. */
1667 Chat.deleteMessageElem(m.mdel);
1668 return;
1669 }
1670 if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
 
 
1671 Chat.playNewMessageSound();
1672 }
1673 const row = new Chat.MessageWidget(m);
1674 Chat.injectMessageElem(row.e.body,atEnd);
1675 if(m.isError){
@@ -1831,13 +1988,13 @@
1831 Chat.chatOnlyMode(true);
1832 }
1833 Chat.intervalTimer = setInterval(poll, 1000);
1834 if(0){
1835 const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1836 document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1837 e.addEventListener('click',flip, false);
1838 });
1839 }
1840 setTimeout( ()=>Chat.inputFocus(), 0 );
1841 Chat.animate.$disabled = false;
1842 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
1843 });
1844
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126 inputWrapper: E1("#chat-input-area"),
127 inputLine: E1('#chat-input-line'),
128 fileSelectWrapper: E1('#chat-input-file-area'),
129 viewMessages: E1('#chat-messages-wrapper'),
130 btnSubmit: E1('#chat-button-submit'),
131 btnAttach: E1('#chat-button-attach'),
132 inputField: E1('#chat-input-field'),
 
133 inputFile: E1('#chat-input-file'),
134 contentDiv: E1('div.content'),
135 viewConfig: E1('#chat-config'),
136 viewPreview: E1('#chat-preview'),
137 previewContent: E1('#chat-preview-content'),
138 btnPreview: E1('#chat-button-preview'),
139 views: document.querySelectorAll('.chat-view'),
140 activeUserListWrapper: E1('#chat-user-list-wrapper'),
141 activeUserList: E1('#chat-user-list'),
142 btnClearFilter: E1('#chat-clear-filter')
143 },
@@ -185,56 +184,23 @@
184 taking into account single- vs multi-line input. The getter returns
185 a string and the setter returns this object. */
186 inputValue: function(){
187 const e = this.inputElement();
188 if(arguments.length){
189 e.innerText = arguments[0];
190 return this;
191 }
192 return e.innerText;
193 },
194 /** Asks the current user input field to take focus. Returns this. */
195 inputFocus: function(){
196 this.inputElement().focus();
197 return this;
198 },
199 /** Returns the current message input element. */
200 inputElement: function(){
201 return this.e.inputField;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202 },
203 /** Enables (if yes is truthy) or disables all elements in
204 * this.disableDuringAjax. */
205 enableAjaxComponents: function(yes){
206 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
375 return e ? overlapsElemView(e, this.e.viewMessages) : false;
376 },
377 settings:{
378 get: (k,dflt)=>F.storage.get(k,dflt),
379 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
380 set: function(k,v){
381 F.storage.set(k,v);
382 F.page.dispatchEvent('chat-setting',{key: k, value: v});
383 },
384 /* Toggles the boolean setting specified by k. Returns the
385 new value.*/
386 toggle: function(k){
387 const v = this.getBool(k);
388 this.set(k, !v);
389 return !v;
390 },
391 addListener: function(setting, f){
392 F.page.addEventListener('chat-setting', function(ev){
393 if(ev.detail.key===setting) f(ev.detail);
394 }, false);
395 },
396 /* Default values of settings. These are used for intializing
397 the setting event listeners and config view UI. */
398 defaults:{
399 /* When on, inbound images are displayed inlined, else as a
400 link to download the image. */
401 "images-inline": !!F.config.chat.imagesInline,
402 /* When on, ctrl-enter sends messages, else enter and
403 ctrl-enter both send them. */
404 "edit-ctrl-send": false,
405 /* When on, the edit field starts as a single line and
406 expands as the user types, and the relevant buttons are
407 laid out in a compact form. When off, the edit field and
408 buttons are larger. */
409 "edit-compact-mode": true,
410 /* When on, sets the font-family on messages and the edit
411 field to monospace. */
412 "monospace-messages": true,
413 /* When on, non-chat UI elements (page header/footer) are
414 hidden */
415 "chat-only-mode": false,
416 /* When set to a URI, it is assumed to be an audio file,
417 which gets played when new messages arrive. When true,
418 the first entry in the audio file selection list will be
419 used. */
420 "audible-alert": true,
421 /* When on, show the list of "active" users - those from
422 whom we have messages in the currently-loaded history
423 (noting that deletions are also messages). */
424 "active-user-list": false,
425 /* When on, the [active-user-list] setting includes the
426 timestamp of each user's most recent message. */
427 "active-user-list-timestamps": false,
428 /* When on, the [audible-alert] is played for one's own
429 messages, else it is only played for other users'
430 messages. */
431 "alert-own-messages": false
432 }
433 },
434 /** Plays a new-message notification sound IF the audible-alert
435 setting is true, else this is a no-op. Returns this.
436 */
@@ -437,11 +439,11 @@
439 try{
440 if(!f.audio) f.audio = new Audio(f.uri);
441 f.audio.currentTime = 0;
442 f.audio.play();
443 }catch(e){
444 console.error("Audio playblack failed.", f.uri, e);
445 }
446 }
447 return this;
448 },
449 /**
@@ -466,11 +468,12 @@
468 return e;
469 }
470 this.e.views.forEach(function(E){
471 if(e!==E) D.addClass(E,'hidden');
472 });
473 this.e.currentView = e;
474 D.removeClass(e,'hidden');
475 this.animate(this.e.currentView, 'anim-fade-in-fast');
476 return this.e.currentView;
477 },
478 /**
479 Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
588 /*Unfortante filter-specific logic*/
589 (e)=>e.classList.remove('selected')
590 );
591 return this;
592 },
593 /**
594 Show or hide the active user list. Returns this object.
595 */
596 showActiveUserList: function(yes){
597 if(0===arguments.length) yes = true;
598 this.e.activeUserListWrapper.classList[
599 yes ? 'remove' : 'add'
600 ]('hidden');
601 D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
602 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
603 /* When hiding this element, undo all filtering */
604 Chat.setUserFilter(false);
605 /*Ideally we'd scroll the final message into view
606 now, but because viewMessages is currently hidden behind
607 viewConfig, scrolling is a no-op. */
608 Chat.scrollMessagesTo(1);
609 }else{
610 Chat.updateActiveUserList();
611 Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
612 }
613 return this;
614 },
615 showActiveUserTimestamps: function(yes){
616 if(0===arguments.length) yes = true;
617 this.e.activeUserList.classList[yes ? 'add' : 'remove']('timestamps');
618 return this;
619 },
620 /**
621 Applies user name filter to all current messages, or clears
622 the filter if uname is falsy.
623 */
624 setUserFilter: function(uname){
@@ -634,38 +664,18 @@
664 D.addClassBriefly(e, a, 0, cb);
665 }
666 return this;
667 }
668 };
669 if(!D.attr(cs.e.inputField,'contenteditable','plaintext-only').isContentEditable){
670 /* Only the Chrome family supports contenteditable=plaintext-only,
671 but Chrome is the only engine for which we need this flag: */
672 D.attr(cs.e.inputField,'contenteditable','true');
673 }
674 cs.animate.$disabled = true;
675 F.fetch.beforesend = ()=>cs.ajaxStart();
676 F.fetch.aftersend = ()=>cs.ajaxEnd();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677 cs.pageTitleOrig = cs.e.pageTitle.innerText;
678 const qs = (e)=>document.querySelector(e);
679 const argsToArray = function(args){
680 return Array.prototype.slice.call(args,0);
681 };
@@ -1234,11 +1244,11 @@
1244 }
1245 };
1246 return cf;
1247 })()/*MessageWidget*/;
1248
1249 const BlobXferState = (function(){
1250 /* State for paste and drag/drop */
1251 const bxs = {
1252 dropDetails: document.querySelector('#chat-drop-details'),
1253 blob: undefined,
1254 clear: function(){
@@ -1249,75 +1259,83 @@
1259 };
1260 /** Updates the paste/drop zone with details of the pasted/dropped
1261 data. The argument must be a Blob or Blob-like object (File) or
1262 it can be falsy to reset/clear that state.*/
1263 const updateDropZoneContent = function(blob){
1264 //console.debug("updateDropZoneContent()",blob);
1265 const dd = bxs.dropDetails;
1266 bxs.blob = blob;
1267 D.clearElement(dd);
1268 if(!blob){
1269 Chat.e.inputFile.value = '';
1270 return;
1271 }
1272 D.append(dd, "Attached: ", blob.name,
1273 D.br(), "Size: ",blob.size);
1274 const btn = D.button("Cancel");
1275 D.append(dd, D.br(), btn);
1276 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1277 if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
1278 const img = D.img();
1279 D.append(dd, D.br(), img);
1280 const reader = new FileReader();
1281 reader.onload = (e)=>img.setAttribute('src', e.target.result);
1282 reader.readAsDataURL(blob);
1283 }
 
 
 
1284 };
1285 Chat.e.inputFile.addEventListener('change', function(ev){
1286 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
1287 });
1288 /* Handle image paste from clipboard. TODO: figure out how we can
1289 paste non-image binary data as if it had been selected via the
1290 file selection element. */
1291 const pasteListener = function(event){
1292 const items = event.clipboardData.items,
1293 item = items[0];
1294 //console.debug("paste event",event.target,item,event);
1295 //console.debug("paste event item",item);
1296 if(item && item.type && ('file'===item.kind || 'BITMAP'===item.type)){
1297 updateDropZoneContent(false/*clear prev state*/);
1298 updateDropZoneContent(item.getAsFile());
1299 event.stopPropagation();
1300 event.preventDefault(true);
1301 return false;
1302 }
1303 /* else continue propagating */
1304 };
1305 document.addEventListener('paste', pasteListener, true);
1306 if(0){
1307 const onPastePlainText = function(ev){
1308 var pastedText = undefined;
1309 if (window.clipboardData && window.clipboardData.getData) { // IE
1310 pastedText = window.clipboardData.getData('Text');
1311 }else if (ev.clipboardData && ev.clipboardData.getData) {
1312 pastedText = ev.clipboardData.getData('text/plain');
1313 }
1314 ev.target.textContent += pastedText;
1315 ev.preventDefault();
1316 return false;
1317 };
1318 Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
1319 }
1320 const noDragDropEvents = function(ev){
1321 /* contenteditable tries to do its own thing with dropped data,
1322 which is not compatible with how we use it, so... */
1323 ev.dataTransfer.effectAllowed = 'none';
1324 ev.dataTransfer.dropEffect = 'none';
1325 ev.preventDefault();
1326 ev.stopPropagation();
1327 return false;
1328 };
1329
1330 ['drop','dragenter','dragleave','dragend'].forEach(
1331 (k)=>{
1332 Chat.inputElement().addEventListener(k, noDragDropEvents, false);
1333 }
1334 );
1335 return bxs;
1336 })()/*drag/drop/paste*/;
1337
1338 const tzOffsetToString = function(off){
1339 const hours = Math.round(off/60), min = Math.round(off % 30);
1340 return ''+(hours + (min ? '.5' : ''));
1341 };
@@ -1335,21 +1353,30 @@
1353 empty, this is a no-op.
1354 */
1355 Chat.submitMessage = function f(){
1356 if(!f.spaces){
1357 f.spaces = /\s+$/;
1358 f.markdownContinuation = /\\\s+$/;
1359 }
1360 this.setCurrentView(this.e.viewMessages);
1361 const fd = new FormData();
1362 var msg = this.inputValue().trim();
1363 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
1364 /* Cosmetic: trim whitespace from the ends of lines to try to
1365 keep copy/paste from terminals, especially wide ones, from
1366 forcing a horizontal scrollbar on all clients. This breaks
1367 markdown's use of blackslash-space-space for paragraph
1368 continuation, but *not* doing this affects all clients every
1369 time someone pastes in console copy/paste from an affected
1370 platform. We seem to have narrowed to the console pasting
1371 problem to users of tmux. Most consoles don't behave
1372 that way. */
1373 const xmsg = msg.split('\n');
1374 xmsg.forEach(function(line,ndx){
1375 if(!f.markdownContinuation.test(line)){
1376 xmsg[ndx] = line.trimRight();
1377 }
1378 });
1379 msg = xmsg.join('\n');
1380 }
1381 if(msg) fd.set('msg',msg);
1382 const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
1401 });
1402 BlobXferState.clear();
1403 Chat.inputValue("").inputFocus();
1404 };
1405
1406 const inputWidgetKeydown = function f(ev){
1407 if(!f.$toggleCtrl){
1408 f.$toggleCtrl = function(currentMode){
1409 currentMode = !currentMode;
1410 Chat.settings.set('edit-ctrl-send', currentMode);
1411 };
1412 f.$toggleCompact = function(currentMode){
1413 currentMode = !currentMode;
1414 Chat.settings.set('edit-compact-mode', currentMode);
1415 };
1416 }
1417 if(13 !== ev.keyCode) return;
1418 const text = Chat.inputValue().trim();
1419 const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
1420 //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
1421 if(ev.shiftKey){
1422 const compactMode = Chat.settings.getBool('edit-compact-mode', false);
1423 ev.preventDefault();
1424 ev.stopPropagation();
1425 /* Shift-enter will run preview mode UNLESS preview mode is
1426 active AND the input field is empty, in which case it will
1427 switch back to message view. */
1428 if(Chat.e.currentView===Chat.e.viewPreview && !text){
1429 Chat.setCurrentView(Chat.e.viewMessages);
1430 }else if(!text){
1431 f.$toggleCompact(compactMode);
1432 }else{
1433 Chat.e.btnPreview.click();
1434 }
1435 return false;
1436 }
1437 if(ev.ctrlKey && !text && !BlobXferState.blob){
1438 /* Ctrl-enter on empty input field(s) toggles Enter/Ctrl-enter mode */
1439 ev.preventDefault();
1440 ev.stopPropagation();
1441 f.$toggleCtrl(ctrlMode);
1442 return false;
1443 }
1444 if(!ctrlMode && ev.ctrlKey && text){
1445 //console.debug("!ctrlMode && ev.ctrlKey && text.");
1446 /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
1447 newline, but that is not happening, for unknown reasons
1448 (possibly related to this element being a conteneditable DIV
1449 instead of a textarea). Forcibly appending a newline do the
1450 input area does not work, also for unknown reasons, and would
1451 only be suitable when we're at the end of the input.
1452
1453 Strangely, this approach DOES work for shift-enter, but we
1454 need shift-enter as a hotkey for preview mode.
1455 */
1456 //return;
1457 // return here "should" cause newline to be added, but that doesn't work
1458 }
1459 if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey/* && ctrlMode*/)){
1460 /* Ship it! */
1461 ev.preventDefault();
1462 ev.stopPropagation();
1463 Chat.submitMessage();
1464 return false;
1465 }
1466 };
1467 Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
 
 
 
1468 Chat.e.btnSubmit.addEventListener('click',(e)=>{
1469 e.preventDefault();
1470 Chat.submitMessage();
1471 return false;
1472 });
1473 Chat.e.btnAttach.addEventListener(
1474 'click', ()=>Chat.e.inputFile.click(), false);
1475
1476 (function(){/*Set up #chat-button-settings and related bits */
1477 if(window.innerWidth<window.innerHeight){
1478 // Must be set up before config view is...
1479 /* Alignment of 'my' messages: right alignment is conventional
1480 for mobile chat apps but can be difficult to read in wide
1481 windows (desktop/tablet landscape mode), so we default to a
1482 layout based on the apparent "orientation" of the window:
1483 tall vs wide. Can be toggled via settings. */
1484 document.body.classList.add('my-messages-right');
1485 }
1486 const settingsButton = document.querySelector('#chat-button-settings');
1487 const optionsMenu = E1('#chat-config-options');
1488 const cbToggle = function(ev){
1489 ev.preventDefault();
1490 ev.stopPropagation();
1491 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
1498 /** Internal acrobatics to allow certain settings toggles to access
1499 related toggles. */
1500 const namedOptions = {
1501 activeUsers:{
1502 label: "Show active users list",
1503 hint: "List users who have messages in the currently-loaded chat history.",
1504 boolValue: 'active-user-list'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1505 }
1506 };
1507 if(1){
1508 /* Per user request, toggle the list of users on and off if the
1509 legend element is tapped. */
@@ -1454,65 +1515,82 @@
1515 if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1516 Chat.animate(optAu.theList,'anim-flip-v');
1517 }
1518 }, false);
1519 }/*namedOptions.activeUsers additional setup*/
1520 /**
1521 Settings options structure: an array of Objects with the
1522 following properties:
1523
1524 label: string for the UI
1525
1526 boolValue: string (name of Chat.settings setting) or a
1527 function which returns true or false. Gets mapped to
1528 a checkbox.
1529
1530 select: SELECT element (instead of boolValue)
1531
1532 callback: optional handler to call after setting is modified.
1533 It gets passed the setting object: {key:string, value:something}.
1534
1535 If a setting has a boolValue set, that gets transformed into a
1536 checkbox which toggles the given persistent setting (if
1537 boolValue is a string) AND listens for changes to that setting
1538 fired via Chat.settings.set() so that the checkbox can stay in
1539 sync with external changes to that setting. Various Chat UI
1540 elements stay in sync with the config UI via those settings
1541 events.
1542 */
1543 const settingsOps = [{
1544 label: "Ctrl-enter to Send",
1545 hint: [
1546 "When on, only Ctrl-Enter will send messages and Enter adds ",
1547 "blank lines. ",
1548 "When off, both Enter and Ctrl-Enter send. ",
1549 "When the input field has focus, is empty, and preview ",
1550 "mode is NOT active then Ctrl-Enter toggles this setting."
1551 ].join(''),
1552 boolValue: 'edit-ctrl-send'
1553 },{
1554 label: "Compact mode",
1555 hint: [
1556 "Toggle between a space-saving or more spacious writing area. ",
1557 "When the input field has focus, is empty, and preview mode ",
1558 "is NOT active then Shift-Enter toggles this setting."
1559 ].join(''),
1560 boolValue: 'edit-compact-mode'
1561 },{
1562 label: "Left-align my posts",
1563 hint: "Default alignment of your own messages is selected "
1564 +"based window width/height relationship.",
1565 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1566 callback: function f(){
1567 document.body.classList[
1568 this.checkbox.checked ? 'remove' : 'add'
1569 ]('my-messages-right');
1570 }
1571 },{
1572 label: "Monospace message font",
1573 hint: "Use monospace font for message text?",
1574 boolValue: 'monospace-messages',
1575 callback: function(setting){
1576 document.body.classList[
1577 setting.value ? 'add' : 'remove'
1578 ]('monospace-messages');
1579 }
1580 },{
1581 label: "Chat-only mode",
1582 hint: "Toggle the page between normal fossil view and chat-only view.",
1583 boolValue: 'chat-only-mode'
1584 },{
1585 label: "Show images inline",
1586 hint: "Whether to show images inline or as a hyperlink.",
1587 boolValue: 'images-inline'
1588 },namedOptions.activeUsers,{
1589 label: "Timestamps in active users list",
1590 hint: "Whether to show last-message timestamps.",
1591 boolValue: 'active-user-list-timestamps'
1592 }];
1593
1594 /** Set up selection list of notification sounds. */
1595 if(1){
1596 const selectSound = D.select();
@@ -1522,91 +1600,168 @@
1600 if(true===Chat.settings.getBool('audible-alert')){
1601 /* This setting used to be a plain bool. If we encounter
1602 such a setting, take the first sound in the list. */
1603 selectSound.selectedIndex = firstSoundIndex;
1604 }else{
1605 selectSound.value = Chat.settings.get('audible-alert','<none>');
1606 if(selectSound.selectedIndex<0){
1607 /* Missing file - removed after this setting was
1608 applied. Fall back to the first sound in the list. */
1609 selectSound.selectedIndex = firstSoundIndex;
1610 }
1611 }
1612 Chat.setNewMessageSound(selectSound.value);
1613 settingsOps.push({
1614 hint: "Audio alert. How to enable audio playback is browser-specific!",
1615 select: selectSound,
1616 callback: function(ev){
1617 const v = ev.target.value;
1618 Chat.setNewMessageSound(v);
1619 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1620 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1621 }
1622 });
1623 }/*audio notification config*/
1624 settingsOps.push({
1625 label: "Play notification for your own messages.",
1626 hint: "When enabled, the audio notification will be played for all messages, "+
1627 "including your own. When disabled only messages from other users "+
1628 "will trigger a notification.",
1629 boolValue: 'alert-own-messages'
1630 });
1631 /**
1632 Build UI for config options...
1633 */
1634 settingsOps.forEach(function f(op){
1635 const line = D.addClass(D.div(), 'menu-entry');
1636 const label = op.label
1637 ? D.append(D.label(),op.label) : undefined;
1638 const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1639 var hint;
1640 const col0 = D.span();
1641 if(op.hint){
1642 hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1643 }
 
1644 if(op.hasOwnProperty('select')){
1645 D.append(line, col0, labelWrapper);
1646 D.append(labelWrapper, op.select);
1647 if(hint) D.append(labelWrapper, hint);
1648 if(label) D.append(col0, label);
1649 if(op.callback){
1650 op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1651 }
1652 }else if(op.hasOwnProperty('boolValue')){
1653 if(undefined === f.$id) f.$id = 0;
1654 ++f.$id;
1655 if('string' ===typeof op.boolValue){
1656 const key = op.boolValue;
1657 op.boolValue = ()=>Chat.settings.getBool(key);
1658 op.persistentSetting = key;
1659 }
1660 const check = op.checkbox
1661 = D.attr(D.checkbox(1, op.boolValue()),
1662 'aria-label', op.label);
1663 const id = 'cfgopt'+f.$id;
1664 check.checked = op.boolValue();
1665 op.checkbox = check;
1666 D.attr(check, 'id', id);
1667 D.append(line, col0, labelWrapper);
1668 D.append(col0, check);
1669 if(label){
1670 D.attr(label, 'for', id);
1671 D.append(labelWrapper, label);
1672 }
1673 if(hint) D.append(labelWrapper, hint);
1674 }else{
1675 line.addEventListener('click', callback);
1676 D.append(line, col0, labelWrapper);
1677 if(label) D.append(labelWrapper, label);
1678 if(hint) D.append(labelWrapper, hint);
1679 }
1680 D.append(optionsMenu, line);
1681 if(op.persistentSetting){
1682 Chat.settings.addListener(
1683 op.persistentSetting,
1684 function(setting){
1685 if(op.checkbox) op.checkbox.checked = !!setting.value;
1686 else if(op.select) op.select.value = setting.value;
1687 if(op.callback) op.callback(setting);
1688 }
1689 );
1690 if(op.checkbox){
1691 op.checkbox.addEventListener(
1692 'change', function(){
1693 Chat.settings.set(op.persistentSetting, op.checkbox.checked)
1694 }, false);
1695 }
1696 }else if(op.callback && op.checkbox){
1697 op.checkbox.addEventListener('change', (ev)=>op.callback(ev), false);
1698 }
1699 });
1700 })()/*#chat-button-settings setup*/;
 
 
 
 
1701
1702 (function(){
1703 /* Install default settings... must come after
1704 chat-button-settings setup so that the listeners which that
1705 installs are notified via the properties getting initialized
1706 here. */
1707 Chat.settings.addListener('monospace-messages',function(s){
1708 document.body.classList[s.value ? 'add' : 'remove']('monospace-messages');
1709 })
1710 Chat.settings.addListener('active-user-list',function(s){
1711 Chat.showActiveUserList(s.value);
1712 });
1713 Chat.settings.addListener('active-user-list-timestamps',function(s){
1714 Chat.showActiveUserTimestamps(s.value);
1715 });
1716 Chat.settings.addListener('chat-only-mode',function(s){
1717 Chat.chatOnlyMode(s.value);
1718 });
1719 Chat.settings.addListener('edit-compact-mode',function(s){
1720 Chat.e.inputLine.classList[
1721 s.value ? 'add' : 'remove'
1722 ]('compact');
1723 });
1724 Chat.settings.addListener('edit-ctrl-send',function(s){
1725 const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1726 const eInput = Chat.inputElement();
1727 eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
1728 Chat.e.btnSubmit.title = label;
1729 });
1730 const valueKludges = {
1731 /* Convert certain string-format values to other types... */
1732 "false": false,
1733 "true": true
1734 };
1735 Object.keys(Chat.settings.defaults).forEach(function(k){
1736 var v = Chat.settings.get(k,Chat);
1737 if(Chat===v) v = Chat.settings.defaults[k];
1738 if(valueKludges.hasOwnProperty(v)) v = valueKludges[v];
1739 Chat.settings.set(k,v)
1740 /* fires event listeners so that the Config area checkboxes
1741 get in sync */;
1742 });
1743 })();
1744
1745 (function(){/*set up message preview*/
1746 const btnPreview = Chat.e.btnPreview;
1747 Chat.setPreviewText = function(t){
1748 this.setCurrentView(this.e.viewPreview);
1749 this.e.previewContent.innerHTML = t;
1750 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1751 setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1752 this.inputFocus();
1753 };
1754 Chat.e.viewPreview.querySelector('#chat-preview-close').
1755 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
1756 let previewPending = false;
1757 const elemsToEnable = [btnPreview, Chat.e.btnSubmit, Chat.e.inputField];
 
 
1758 const submit = function(ev){
1759 ev.preventDefault();
1760 ev.stopPropagation();
1761 if(previewPending) return false;
1762 const txt = Chat.inputValue();
1763 if(!txt){
1764 Chat.setPreviewText('');
1765 previewPending = false;
1766 return false;
1767 }
@@ -1665,11 +1820,13 @@
1820 if( m.mdel ){
1821 /* A record deletion notice. */
1822 Chat.deleteMessageElem(m.mdel);
1823 return;
1824 }
1825 if(!Chat._isBatchLoading
1826 && (Chat.me!==m.xfrom
1827 || Chat.settings.getBool('alert-own-messages'))){
1828 Chat.playNewMessageSound();
1829 }
1830 const row = new Chat.MessageWidget(m);
1831 Chat.injectMessageElem(row.e.body,atEnd);
1832 if(m.isError){
@@ -1831,13 +1988,13 @@
1988 Chat.chatOnlyMode(true);
1989 }
1990 Chat.intervalTimer = setInterval(poll, 1000);
1991 if(0){
1992 const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1993 document.querySelectorAll('#chat-buttons-wrapper .cbutton').forEach(function(e){
1994 e.addEventListener('click',flip, false);
1995 });
1996 }
1997 setTimeout( ()=>Chat.inputFocus(), 0 );
1998 Chat.animate.$disabled = false;
1999 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
2000 });
2001
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125125
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126126
inputWrapper: E1("#chat-input-area"),
127127
inputLine: E1('#chat-input-line'),
128128
fileSelectWrapper: E1('#chat-input-file-area'),
129129
viewMessages: E1('#chat-messages-wrapper'),
130
- btnSubmit: E1('#chat-message-submit'),
131
- inputSingle: E1('#chat-input-single'),
132
- inputMulti: E1('#chat-input-multi'),
133
- inputCurrent: undefined/*one of inputSingle or inputMulti*/,
130
+ btnSubmit: E1('#chat-button-submit'),
131
+ btnAttach: E1('#chat-button-attach'),
132
+ inputField: E1('#chat-input-field'),
134133
inputFile: E1('#chat-input-file'),
135134
contentDiv: E1('div.content'),
136135
viewConfig: E1('#chat-config'),
137136
viewPreview: E1('#chat-preview'),
138137
previewContent: E1('#chat-preview-content'),
139
- btnPreview: E1('#chat-preview-button'),
138
+ btnPreview: E1('#chat-button-preview'),
140139
views: document.querySelectorAll('.chat-view'),
141140
activeUserListWrapper: E1('#chat-user-list-wrapper'),
142141
activeUserList: E1('#chat-user-list'),
143142
btnClearFilter: E1('#chat-clear-filter')
144143
},
@@ -185,56 +184,23 @@
185184
taking into account single- vs multi-line input. The getter returns
186185
a string and the setter returns this object. */
187186
inputValue: function(){
188187
const e = this.inputElement();
189188
if(arguments.length){
190
- e.value = arguments[0];
189
+ e.innerText = arguments[0];
191190
return this;
192191
}
193
- return e.value;
192
+ return e.innerText;
194193
},
195194
/** Asks the current user input field to take focus. Returns this. */
196195
inputFocus: function(){
197196
this.inputElement().focus();
198197
return this;
199198
},
200199
/** Returns the current message input element. */
201200
inputElement: function(){
202
- return this.e.inputCurrent;
203
- },
204
- /** Toggles between single- and multi-line edit modes. Returns this. */
205
- inputToggleSingleMulti: function(){
206
- const old = this.e.inputCurrent;
207
- if(this.e.inputCurrent === this.e.inputSingle){
208
- this.e.inputCurrent = this.e.inputMulti;
209
- this.e.inputLine.classList.remove('single-line');
210
- }else{
211
- this.e.inputCurrent = this.e.inputSingle;
212
- this.e.inputLine.classList.add('single-line');
213
- }
214
- const m = this.e.viewMessages,
215
- sTop = m.scrollTop,
216
- mh1 = m.clientHeight;
217
- D.addClass(old, 'hidden');
218
- D.removeClass(this.e.inputCurrent, 'hidden');
219
- const mh2 = m.clientHeight;
220
- m.scrollTo(0, sTop + (mh1-mh2));
221
- this.e.inputCurrent.value = old.value;
222
- old.value = '';
223
- return this;
224
- },
225
- /**
226
- If passed true or no arguments, switches to multi-line mode
227
- if currently in single-line mode. If passed false, switches
228
- to single-line mode if currently in multi-line mode. Returns
229
- this.
230
- */
231
- inputMultilineMode: function(yes){
232
- if(!arguments.length) yes = true;
233
- if(yes && this.e.inputCurrent === this.e.inputMulti) return this;
234
- else if(!yes && this.e.inputCurrent === this.e.inputSingle) return this;
235
- else return this.inputToggleSingleMulti();
201
+ return this.e.inputField;
236202
},
237203
/** Enables (if yes is truthy) or disables all elements in
238204
* this.disableDuringAjax. */
239205
enableAjaxComponents: function(yes){
240206
D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
409375
return e ? overlapsElemView(e, this.e.viewMessages) : false;
410376
},
411377
settings:{
412378
get: (k,dflt)=>F.storage.get(k,dflt),
413379
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
414
- set: (k,v)=>F.storage.set(k,v),
380
+ set: function(k,v){
381
+ F.storage.set(k,v);
382
+ F.page.dispatchEvent('chat-setting',{key: k, value: v});
383
+ },
415384
/* Toggles the boolean setting specified by k. Returns the
416385
new value.*/
417386
toggle: function(k){
418387
const v = this.getBool(k);
419388
this.set(k, !v);
420389
return !v;
421390
},
391
+ addListener: function(setting, f){
392
+ F.page.addEventListener('chat-setting', function(ev){
393
+ if(ev.detail.key===setting) f(ev.detail);
394
+ }, false);
395
+ },
396
+ /* Default values of settings. These are used for intializing
397
+ the setting event listeners and config view UI. */
422398
defaults:{
399
+ /* When on, inbound images are displayed inlined, else as a
400
+ link to download the image. */
423401
"images-inline": !!F.config.chat.imagesInline,
424
- "edit-multiline": false,
425
- "monospace-messages": false,
402
+ /* When on, ctrl-enter sends messages, else enter and
403
+ ctrl-enter both send them. */
404
+ "edit-ctrl-send": false,
405
+ /* When on, the edit field starts as a single line and
406
+ expands as the user types, and the relevant buttons are
407
+ laid out in a compact form. When off, the edit field and
408
+ buttons are larger. */
409
+ "edit-compact-mode": true,
410
+ /* When on, sets the font-family on messages and the edit
411
+ field to monospace. */
412
+ "monospace-messages": true,
413
+ /* When on, non-chat UI elements (page header/footer) are
414
+ hidden */
426415
"chat-only-mode": false,
416
+ /* When set to a URI, it is assumed to be an audio file,
417
+ which gets played when new messages arrive. When true,
418
+ the first entry in the audio file selection list will be
419
+ used. */
427420
"audible-alert": true,
421
+ /* When on, show the list of "active" users - those from
422
+ whom we have messages in the currently-loaded history
423
+ (noting that deletions are also messages). */
428424
"active-user-list": false,
429
- "active-user-list-timestamps": false
425
+ /* When on, the [active-user-list] setting includes the
426
+ timestamp of each user's most recent message. */
427
+ "active-user-list-timestamps": false,
428
+ /* When on, the [audible-alert] is played for one's own
429
+ messages, else it is only played for other users'
430
+ messages. */
431
+ "alert-own-messages": false
430432
}
431433
},
432434
/** Plays a new-message notification sound IF the audible-alert
433435
setting is true, else this is a no-op. Returns this.
434436
*/
@@ -437,11 +439,11 @@
437439
try{
438440
if(!f.audio) f.audio = new Audio(f.uri);
439441
f.audio.currentTime = 0;
440442
f.audio.play();
441443
}catch(e){
442
- console.error("Audio playblack failed.",e);
444
+ console.error("Audio playblack failed.", f.uri, e);
443445
}
444446
}
445447
return this;
446448
},
447449
/**
@@ -466,11 +468,12 @@
466468
return e;
467469
}
468470
this.e.views.forEach(function(E){
469471
if(e!==E) D.addClass(E,'hidden');
470472
});
471
- this.e.currentView = D.removeClass(e,'hidden');
473
+ this.e.currentView = e;
474
+ D.removeClass(e,'hidden');
472475
this.animate(this.e.currentView, 'anim-fade-in-fast');
473476
return this.e.currentView;
474477
},
475478
/**
476479
Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
585588
/*Unfortante filter-specific logic*/
586589
(e)=>e.classList.remove('selected')
587590
);
588591
return this;
589592
},
593
+ /**
594
+ Show or hide the active user list. Returns this object.
595
+ */
596
+ showActiveUserList: function(yes){
597
+ if(0===arguments.length) yes = true;
598
+ this.e.activeUserListWrapper.classList[
599
+ yes ? 'remove' : 'add'
600
+ ]('hidden');
601
+ D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
602
+ if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
603
+ /* When hiding this element, undo all filtering */
604
+ Chat.setUserFilter(false);
605
+ /*Ideally we'd scroll the final message into view
606
+ now, but because viewMessages is currently hidden behind
607
+ viewConfig, scrolling is a no-op. */
608
+ Chat.scrollMessagesTo(1);
609
+ }else{
610
+ Chat.updateActiveUserList();
611
+ Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
612
+ }
613
+ return this;
614
+ },
615
+ showActiveUserTimestamps: function(yes){
616
+ if(0===arguments.length) yes = true;
617
+ this.e.activeUserList.classList[yes ? 'add' : 'remove']('timestamps');
618
+ return this;
619
+ },
590620
/**
591621
Applies user name filter to all current messages, or clears
592622
the filter if uname is falsy.
593623
*/
594624
setUserFilter: function(uname){
@@ -634,38 +664,18 @@
634664
D.addClassBriefly(e, a, 0, cb);
635665
}
636666
return this;
637667
}
638668
};
669
+ if(!D.attr(cs.e.inputField,'contenteditable','plaintext-only').isContentEditable){
670
+ /* Only the Chrome family supports contenteditable=plaintext-only,
671
+ but Chrome is the only engine for which we need this flag: */
672
+ D.attr(cs.e.inputField,'contenteditable','true');
673
+ }
639674
cs.animate.$disabled = true;
640675
F.fetch.beforesend = ()=>cs.ajaxStart();
641676
F.fetch.aftersend = ()=>cs.ajaxEnd();
642
- cs.e.inputCurrent = cs.e.inputSingle;
643
- /* Install default settings... */
644
- Object.keys(cs.settings.defaults).forEach(function(k){
645
- const v = cs.settings.get(k,cs);
646
- if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
647
- });
648
- if(window.innerWidth<window.innerHeight){
649
- /* Alignment of 'my' messages: right alignment is conventional
650
- for mobile chat apps but can be difficult to read in wide
651
- windows (desktop/tablet landscape mode), so we default to a
652
- layout based on the apparent "orientation" of the window:
653
- tall vs wide. Can be toggled via settings popup. */
654
- document.body.classList.add('my-messages-right');
655
- }
656
- if(cs.settings.getBool('monospace-messages',false)){
657
- document.body.classList.add('monospace-messages');
658
- }
659
- if(cs.settings.getBool('active-user-list',false)){
660
- cs.e.activeUserListWrapper.classList.remove('hidden');
661
- }
662
- if(cs.settings.getBool('active-user-list-timestamps',false)){
663
- cs.e.activeUserList.classList.add('timestamps');
664
- }
665
- cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
666
- cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
667677
cs.pageTitleOrig = cs.e.pageTitle.innerText;
668678
const qs = (e)=>document.querySelector(e);
669679
const argsToArray = function(args){
670680
return Array.prototype.slice.call(args,0);
671681
};
@@ -1234,11 +1244,11 @@
12341244
}
12351245
};
12361246
return cf;
12371247
})()/*MessageWidget*/;
12381248
1239
- const BlobXferState = (function(){/*drag/drop bits...*/
1249
+ const BlobXferState = (function(){
12401250
/* State for paste and drag/drop */
12411251
const bxs = {
12421252
dropDetails: document.querySelector('#chat-drop-details'),
12431253
blob: undefined,
12441254
clear: function(){
@@ -1249,75 +1259,83 @@
12491259
};
12501260
/** Updates the paste/drop zone with details of the pasted/dropped
12511261
data. The argument must be a Blob or Blob-like object (File) or
12521262
it can be falsy to reset/clear that state.*/
12531263
const updateDropZoneContent = function(blob){
1264
+ //console.debug("updateDropZoneContent()",blob);
12541265
const dd = bxs.dropDetails;
12551266
bxs.blob = blob;
12561267
D.clearElement(dd);
12571268
if(!blob){
12581269
Chat.e.inputFile.value = '';
12591270
return;
12601271
}
1261
- D.append(dd, "Name: ", blob.name,
1272
+ D.append(dd, "Attached: ", blob.name,
12621273
D.br(), "Size: ",blob.size);
1263
- if(blob.type && blob.type.startsWith("image/")){
1274
+ const btn = D.button("Cancel");
1275
+ D.append(dd, D.br(), btn);
1276
+ btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1277
+ if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
12641278
const img = D.img();
12651279
D.append(dd, D.br(), img);
12661280
const reader = new FileReader();
12671281
reader.onload = (e)=>img.setAttribute('src', e.target.result);
12681282
reader.readAsDataURL(blob);
12691283
}
1270
- const btn = D.button("Cancel");
1271
- D.append(dd, D.br(), btn);
1272
- btn.addEventListener('click', ()=>updateDropZoneContent(), false);
12731284
};
12741285
Chat.e.inputFile.addEventListener('change', function(ev){
12751286
updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
12761287
});
12771288
/* Handle image paste from clipboard. TODO: figure out how we can
12781289
paste non-image binary data as if it had been selected via the
12791290
file selection element. */
1280
- document.addEventListener('paste', function(event){
1291
+ const pasteListener = function(event){
12811292
const items = event.clipboardData.items,
12821293
item = items[0];
1283
- if(!item || !item.type) return;
1284
- else if('file'===item.kind){
1294
+ //console.debug("paste event",event.target,item,event);
1295
+ //console.debug("paste event item",item);
1296
+ if(item && item.type && ('file'===item.kind || 'BITMAP'===item.type)){
12851297
updateDropZoneContent(false/*clear prev state*/);
12861298
updateDropZoneContent(item.getAsFile());
1287
- }
1288
- }, false);
1289
- /* Add help button for drag/drop/paste zone */
1290
- Chat.e.inputFile.parentNode.insertBefore(
1291
- F.helpButtonlets.create(
1292
- Chat.e.fileSelectWrapper.querySelector('.help-buttonlet')
1293
- ), Chat.e.inputFile
1294
- );
1295
- ////////////////////////////////////////////////////////////
1296
- // File drag/drop visual notification.
1297
- const dropHighlight = Chat.e.inputFile /* target zone */;
1298
- const dropEvents = {
1299
- drop: function(ev){
1300
- D.removeClass(dropHighlight, 'dragover');
1301
- },
1302
- dragenter: function(ev){
1299
+ event.stopPropagation();
1300
+ event.preventDefault(true);
1301
+ return false;
1302
+ }
1303
+ /* else continue propagating */
1304
+ };
1305
+ document.addEventListener('paste', pasteListener, true);
1306
+ if(0){
1307
+ const onPastePlainText = function(ev){
1308
+ var pastedText = undefined;
1309
+ if (window.clipboardData && window.clipboardData.getData) { // IE
1310
+ pastedText = window.clipboardData.getData('Text');
1311
+ }else if (ev.clipboardData && ev.clipboardData.getData) {
1312
+ pastedText = ev.clipboardData.getData('text/plain');
1313
+ }
1314
+ ev.target.textContent += pastedText;
13031315
ev.preventDefault();
1304
- ev.dataTransfer.dropEffect = "copy";
1305
- D.addClass(dropHighlight, 'dragover');
1306
- },
1307
- dragleave: function(ev){
1308
- D.removeClass(dropHighlight, 'dragover');
1309
- },
1310
- dragend: function(ev){
1311
- D.removeClass(dropHighlight, 'dragover');
1312
- }
1316
+ return false;
1317
+ };
1318
+ Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
1319
+ }
1320
+ const noDragDropEvents = function(ev){
1321
+ /* contenteditable tries to do its own thing with dropped data,
1322
+ which is not compatible with how we use it, so... */
1323
+ ev.dataTransfer.effectAllowed = 'none';
1324
+ ev.dataTransfer.dropEffect = 'none';
1325
+ ev.preventDefault();
1326
+ ev.stopPropagation();
1327
+ return false;
13131328
};
1314
- Object.keys(dropEvents).forEach(
1315
- (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
1329
+
1330
+ ['drop','dragenter','dragleave','dragend'].forEach(
1331
+ (k)=>{
1332
+ Chat.inputElement().addEventListener(k, noDragDropEvents, false);
1333
+ }
13161334
);
13171335
return bxs;
1318
- })()/*drag/drop*/;
1336
+ })()/*drag/drop/paste*/;
13191337
13201338
const tzOffsetToString = function(off){
13211339
const hours = Math.round(off/60), min = Math.round(off % 30);
13221340
return ''+(hours + (min ? '.5' : ''));
13231341
};
@@ -1335,21 +1353,30 @@
13351353
empty, this is a no-op.
13361354
*/
13371355
Chat.submitMessage = function f(){
13381356
if(!f.spaces){
13391357
f.spaces = /\s+$/;
1358
+ f.markdownContinuation = /\\\s+$/;
13401359
}
13411360
this.setCurrentView(this.e.viewMessages);
13421361
const fd = new FormData();
13431362
var msg = this.inputValue().trim();
13441363
if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
13451364
/* Cosmetic: trim whitespace from the ends of lines to try to
13461365
keep copy/paste from terminals, especially wide ones, from
1347
- forcing a horizontal scrollbar on all clients. */
1366
+ forcing a horizontal scrollbar on all clients. This breaks
1367
+ markdown's use of blackslash-space-space for paragraph
1368
+ continuation, but *not* doing this affects all clients every
1369
+ time someone pastes in console copy/paste from an affected
1370
+ platform. We seem to have narrowed to the console pasting
1371
+ problem to users of tmux. Most consoles don't behave
1372
+ that way. */
13481373
const xmsg = msg.split('\n');
13491374
xmsg.forEach(function(line,ndx){
1350
- xmsg[ndx] = line.trimRight();
1375
+ if(!f.markdownContinuation.test(line)){
1376
+ xmsg[ndx] = line.trimRight();
1377
+ }
13511378
});
13521379
msg = xmsg.join('\n');
13531380
}
13541381
if(msg) fd.set('msg',msg);
13551382
const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
13741401
});
13751402
BlobXferState.clear();
13761403
Chat.inputValue("").inputFocus();
13771404
};
13781405
1379
- const inputWidgetKeydown = function(ev){
1380
- if(13 === ev.keyCode){
1381
- if(ev.shiftKey){
1382
- ev.preventDefault();
1383
- ev.stopPropagation();
1406
+ const inputWidgetKeydown = function f(ev){
1407
+ if(!f.$toggleCtrl){
1408
+ f.$toggleCtrl = function(currentMode){
1409
+ currentMode = !currentMode;
1410
+ Chat.settings.set('edit-ctrl-send', currentMode);
1411
+ };
1412
+ f.$toggleCompact = function(currentMode){
1413
+ currentMode = !currentMode;
1414
+ Chat.settings.set('edit-compact-mode', currentMode);
1415
+ };
1416
+ }
1417
+ if(13 !== ev.keyCode) return;
1418
+ const text = Chat.inputValue().trim();
1419
+ const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
1420
+ //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
1421
+ if(ev.shiftKey){
1422
+ const compactMode = Chat.settings.getBool('edit-compact-mode', false);
1423
+ ev.preventDefault();
1424
+ ev.stopPropagation();
1425
+ /* Shift-enter will run preview mode UNLESS preview mode is
1426
+ active AND the input field is empty, in which case it will
1427
+ switch back to message view. */
1428
+ if(Chat.e.currentView===Chat.e.viewPreview && !text){
1429
+ Chat.setCurrentView(Chat.e.viewMessages);
1430
+ }else if(!text){
1431
+ f.$toggleCompact(compactMode);
1432
+ }else{
13841433
Chat.e.btnPreview.click();
1385
- return false;
1386
- }else if((Chat.e.inputSingle===ev.target)
1387
- || (ev.ctrlKey && Chat.e.inputMulti===ev.target)){
1388
- /* ^^^ note that it is intended that both ctrl-enter and enter
1389
- work for single-line input mode. */
1390
- ev.preventDefault();
1391
- ev.stopPropagation();
1392
- Chat.submitMessage();
1393
- return false;
1394
- }
1434
+ }
1435
+ return false;
1436
+ }
1437
+ if(ev.ctrlKey && !text && !BlobXferState.blob){
1438
+ /* Ctrl-enter on empty input field(s) toggles Enter/Ctrl-enter mode */
1439
+ ev.preventDefault();
1440
+ ev.stopPropagation();
1441
+ f.$toggleCtrl(ctrlMode);
1442
+ return false;
1443
+ }
1444
+ if(!ctrlMode && ev.ctrlKey && text){
1445
+ //console.debug("!ctrlMode && ev.ctrlKey && text.");
1446
+ /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
1447
+ newline, but that is not happening, for unknown reasons
1448
+ (possibly related to this element being a conteneditable DIV
1449
+ instead of a textarea). Forcibly appending a newline do the
1450
+ input area does not work, also for unknown reasons, and would
1451
+ only be suitable when we're at the end of the input.
1452
+
1453
+ Strangely, this approach DOES work for shift-enter, but we
1454
+ need shift-enter as a hotkey for preview mode.
1455
+ */
1456
+ //return;
1457
+ // return here "should" cause newline to be added, but that doesn't work
1458
+ }
1459
+ if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey/* && ctrlMode*/)){
1460
+ /* Ship it! */
1461
+ ev.preventDefault();
1462
+ ev.stopPropagation();
1463
+ Chat.submitMessage();
1464
+ return false;
13951465
}
13961466
};
1397
- Chat.e.inputSingle
1398
- .addEventListener('keydown', inputWidgetKeydown, false);
1399
- Chat.e.inputMulti
1400
- .addEventListener('keydown', inputWidgetKeydown, false);
1467
+ Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
14011468
Chat.e.btnSubmit.addEventListener('click',(e)=>{
14021469
e.preventDefault();
14031470
Chat.submitMessage();
14041471
return false;
14051472
});
1473
+ Chat.e.btnAttach.addEventListener(
1474
+ 'click', ()=>Chat.e.inputFile.click(), false);
14061475
1407
- (function(){/*Set up #chat-settings-button */
1408
- const settingsButton = document.querySelector('#chat-settings-button');
1476
+ (function(){/*Set up #chat-button-settings and related bits */
1477
+ if(window.innerWidth<window.innerHeight){
1478
+ // Must be set up before config view is...
1479
+ /* Alignment of 'my' messages: right alignment is conventional
1480
+ for mobile chat apps but can be difficult to read in wide
1481
+ windows (desktop/tablet landscape mode), so we default to a
1482
+ layout based on the apparent "orientation" of the window:
1483
+ tall vs wide. Can be toggled via settings. */
1484
+ document.body.classList.add('my-messages-right');
1485
+ }
1486
+ const settingsButton = document.querySelector('#chat-button-settings');
14091487
const optionsMenu = E1('#chat-config-options');
14101488
const cbToggle = function(ev){
14111489
ev.preventDefault();
14121490
ev.stopPropagation();
14131491
Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
14201498
/** Internal acrobatics to allow certain settings toggles to access
14211499
related toggles. */
14221500
const namedOptions = {
14231501
activeUsers:{
14241502
label: "Show active users list",
1425
- boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1426
- persistentSetting: 'active-user-list',
1427
- callback: function(){
1428
- D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1429
- D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1430
- if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1431
- /* When hiding this element, undo user filtering */
1432
- if(Chat.filter.current === Chat.filter.user){
1433
- Chat.setUserFilter(false);
1434
- }
1435
- /*Ideally we'd scroll the final message into view
1436
- now, but because viewMessages is currently hidden behind
1437
- viewConfig, scrolling is a no-op. */
1438
- Chat.scrollMessagesTo(1);
1439
- }else{
1440
- Chat.updateActiveUserList();
1441
- Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1442
- }
1443
- }
1503
+ hint: "List users who have messages in the currently-loaded chat history.",
1504
+ boolValue: 'active-user-list'
14441505
}
14451506
};
14461507
if(1){
14471508
/* Per user request, toggle the list of users on and off if the
14481509
legend element is tapped. */
@@ -1454,65 +1515,82 @@
14541515
if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
14551516
Chat.animate(optAu.theList,'anim-flip-v');
14561517
}
14571518
}, false);
14581519
}/*namedOptions.activeUsers additional setup*/
1459
- /* Settings menu entries... Remember that they will be rendered in
1460
- reverse order and the most frequently-needed ones "should"
1461
- (arguably) be closer to the start of this list so that they
1462
- will be rendered within easier reach of the settings button. */
1463
- const settingsOps = [{
1464
- label: "Multi-line input",
1465
- boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1466
- persistentSetting: 'edit-multiline',
1467
- callback: function(){
1468
- Chat.inputToggleSingleMulti();
1469
- }
1470
- },{
1471
- label: "Left-align my posts",
1472
- boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1473
- callback: function f(){
1474
- document.body.classList.toggle('my-messages-right');
1475
- }
1476
- },{
1477
- label: "Show images inline",
1478
- boolValue: ()=>Chat.settings.getBool('images-inline'),
1479
- callback: function(){
1480
- const v = Chat.settings.toggle('images-inline');
1481
- F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1482
- }
1483
- },{
1484
- label: "Timestamps in active users list",
1485
- boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1486
- persistentSetting: 'active-user-list-timestamps',
1487
- callback: function(){
1488
- D.toggleClass(Chat.e.activeUserList,'timestamps');
1489
- /* If the timestamp option is activated but
1490
- namedOptions.activeUsers is not currently checked then
1491
- toggle that option on as well. */
1492
- if(Chat.e.activeUserList.classList.contains('timestamps')
1493
- && !namedOptions.activeUsers.boolValue()){
1494
- namedOptions.activeUsers.checkbox.checked = true;
1495
- namedOptions.activeUsers.callback();
1496
- Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1497
- }
1498
- }
1499
- },
1500
- namedOptions.activeUsers,{
1501
- label: "Monospace message font",
1502
- boolValue: ()=>document.body.classList.contains('monospace-messages'),
1503
- persistentSetting: 'monospace-messages',
1504
- callback: function(){
1505
- document.body.classList.toggle('monospace-messages');
1520
+ /**
1521
+ Settings options structure: an array of Objects with the
1522
+ following properties:
1523
+
1524
+ label: string for the UI
1525
+
1526
+ boolValue: string (name of Chat.settings setting) or a
1527
+ function which returns true or false. Gets mapped to
1528
+ a checkbox.
1529
+
1530
+ select: SELECT element (instead of boolValue)
1531
+
1532
+ callback: optional handler to call after setting is modified.
1533
+ It gets passed the setting object: {key:string, value:something}.
1534
+
1535
+ If a setting has a boolValue set, that gets transformed into a
1536
+ checkbox which toggles the given persistent setting (if
1537
+ boolValue is a string) AND listens for changes to that setting
1538
+ fired via Chat.settings.set() so that the checkbox can stay in
1539
+ sync with external changes to that setting. Various Chat UI
1540
+ elements stay in sync with the config UI via those settings
1541
+ events.
1542
+ */
1543
+ const settingsOps = [{
1544
+ label: "Ctrl-enter to Send",
1545
+ hint: [
1546
+ "When on, only Ctrl-Enter will send messages and Enter adds ",
1547
+ "blank lines. ",
1548
+ "When off, both Enter and Ctrl-Enter send. ",
1549
+ "When the input field has focus, is empty, and preview ",
1550
+ "mode is NOT active then Ctrl-Enter toggles this setting."
1551
+ ].join(''),
1552
+ boolValue: 'edit-ctrl-send'
1553
+ },{
1554
+ label: "Compact mode",
1555
+ hint: [
1556
+ "Toggle between a space-saving or more spacious writing area. ",
1557
+ "When the input field has focus, is empty, and preview mode ",
1558
+ "is NOT active then Shift-Enter toggles this setting."
1559
+ ].join(''),
1560
+ boolValue: 'edit-compact-mode'
1561
+ },{
1562
+ label: "Left-align my posts",
1563
+ hint: "Default alignment of your own messages is selected "
1564
+ +"based window width/height relationship.",
1565
+ boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1566
+ callback: function f(){
1567
+ document.body.classList[
1568
+ this.checkbox.checked ? 'remove' : 'add'
1569
+ ]('my-messages-right');
1570
+ }
1571
+ },{
1572
+ label: "Monospace message font",
1573
+ hint: "Use monospace font for message text?",
1574
+ boolValue: 'monospace-messages',
1575
+ callback: function(setting){
1576
+ document.body.classList[
1577
+ setting.value ? 'add' : 'remove'
1578
+ ]('monospace-messages');
15061579
}
15071580
},{
15081581
label: "Chat-only mode",
1509
- boolValue: ()=>Chat.isChatOnlyMode(),
1510
- persistentSetting: 'chat-only-mode',
1511
- callback: function(){
1512
- Chat.toggleChatOnlyMode();
1513
- }
1582
+ hint: "Toggle the page between normal fossil view and chat-only view.",
1583
+ boolValue: 'chat-only-mode'
1584
+ },{
1585
+ label: "Show images inline",
1586
+ hint: "Whether to show images inline or as a hyperlink.",
1587
+ boolValue: 'images-inline'
1588
+ },namedOptions.activeUsers,{
1589
+ label: "Timestamps in active users list",
1590
+ hint: "Whether to show last-message timestamps.",
1591
+ boolValue: 'active-user-list-timestamps'
15141592
}];
15151593
15161594
/** Set up selection list of notification sounds. */
15171595
if(1){
15181596
const selectSound = D.select();
@@ -1522,91 +1600,168 @@
15221600
if(true===Chat.settings.getBool('audible-alert')){
15231601
/* This setting used to be a plain bool. If we encounter
15241602
such a setting, take the first sound in the list. */
15251603
selectSound.selectedIndex = firstSoundIndex;
15261604
}else{
1527
- selectSound.value = Chat.settings.get('audible-alert','');
1605
+ selectSound.value = Chat.settings.get('audible-alert','<none>');
15281606
if(selectSound.selectedIndex<0){
15291607
/* Missing file - removed after this setting was
15301608
applied. Fall back to the first sound in the list. */
15311609
selectSound.selectedIndex = firstSoundIndex;
15321610
}
15331611
}
15341612
Chat.setNewMessageSound(selectSound.value);
15351613
settingsOps.push({
1536
- label: "Audio alert",
1614
+ hint: "Audio alert. How to enable audio playback is browser-specific!",
15371615
select: selectSound,
15381616
callback: function(ev){
15391617
const v = ev.target.value;
15401618
Chat.setNewMessageSound(v);
15411619
F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
15421620
if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
15431621
}
15441622
});
15451623
}/*audio notification config*/
1624
+ settingsOps.push({
1625
+ label: "Play notification for your own messages.",
1626
+ hint: "When enabled, the audio notification will be played for all messages, "+
1627
+ "including your own. When disabled only messages from other users "+
1628
+ "will trigger a notification.",
1629
+ boolValue: 'alert-own-messages'
1630
+ });
15461631
/**
15471632
Build UI for config options...
15481633
*/
15491634
settingsOps.forEach(function f(op){
15501635
const line = D.addClass(D.div(), 'menu-entry');
1551
- const btn = D.append(
1552
- D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1553
- op.label);
1554
- const callback = function(ev){
1555
- op.callback(ev);
1556
- if(op.persistentSetting){
1557
- Chat.settings.set(op.persistentSetting, op.boolValue());
1558
- }
1559
- };
1636
+ const label = op.label
1637
+ ? D.append(D.label(),op.label) : undefined;
1638
+ const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1639
+ var hint;
1640
+ const col0 = D.span();
1641
+ if(op.hint){
1642
+ hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1643
+ }
15601644
if(op.hasOwnProperty('select')){
1561
- D.append(line, btn, op.select);
1562
- op.select.addEventListener('change', callback, false);
1645
+ D.append(line, col0, labelWrapper);
1646
+ D.append(labelWrapper, op.select);
1647
+ if(hint) D.append(labelWrapper, hint);
1648
+ if(label) D.append(col0, label);
1649
+ if(op.callback){
1650
+ op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1651
+ }
15631652
}else if(op.hasOwnProperty('boolValue')){
15641653
if(undefined === f.$id) f.$id = 0;
15651654
++f.$id;
1655
+ if('string' ===typeof op.boolValue){
1656
+ const key = op.boolValue;
1657
+ op.boolValue = ()=>Chat.settings.getBool(key);
1658
+ op.persistentSetting = key;
1659
+ }
15661660
const check = op.checkbox
15671661
= D.attr(D.checkbox(1, op.boolValue()),
15681662
'aria-label', op.label);
15691663
const id = 'cfgopt'+f.$id;
1570
- if(op.boolValue()) check.checked = true;
1664
+ check.checked = op.boolValue();
1665
+ op.checkbox = check;
15711666
D.attr(check, 'id', id);
1572
- D.attr(btn, 'for', id);
1573
- D.append(line, check);
1574
- check.addEventListener('change', callback);
1575
- D.append(line, btn);
1667
+ D.append(line, col0, labelWrapper);
1668
+ D.append(col0, check);
1669
+ if(label){
1670
+ D.attr(label, 'for', id);
1671
+ D.append(labelWrapper, label);
1672
+ }
1673
+ if(hint) D.append(labelWrapper, hint);
15761674
}else{
15771675
line.addEventListener('click', callback);
1578
- D.append(line, btn);
1676
+ D.append(line, col0, labelWrapper);
1677
+ if(label) D.append(labelWrapper, label);
1678
+ if(hint) D.append(labelWrapper, hint);
15791679
}
15801680
D.append(optionsMenu, line);
1681
+ if(op.persistentSetting){
1682
+ Chat.settings.addListener(
1683
+ op.persistentSetting,
1684
+ function(setting){
1685
+ if(op.checkbox) op.checkbox.checked = !!setting.value;
1686
+ else if(op.select) op.select.value = setting.value;
1687
+ if(op.callback) op.callback(setting);
1688
+ }
1689
+ );
1690
+ if(op.checkbox){
1691
+ op.checkbox.addEventListener(
1692
+ 'change', function(){
1693
+ Chat.settings.set(op.persistentSetting, op.checkbox.checked)
1694
+ }, false);
1695
+ }
1696
+ }else if(op.callback && op.checkbox){
1697
+ op.checkbox.addEventListener('change', (ev)=>op.callback(ev), false);
1698
+ }
15811699
});
1582
- if(0 && settingsOps.selectSound){
1583
- D.append(optionsMenu, settingsOps.selectSound);
1584
- }
1585
- //settingsButton.click()/*for for development*/;
1586
- })()/*#chat-settings-button setup*/;
1700
+ })()/*#chat-button-settings setup*/;
15871701
1702
+ (function(){
1703
+ /* Install default settings... must come after
1704
+ chat-button-settings setup so that the listeners which that
1705
+ installs are notified via the properties getting initialized
1706
+ here. */
1707
+ Chat.settings.addListener('monospace-messages',function(s){
1708
+ document.body.classList[s.value ? 'add' : 'remove']('monospace-messages');
1709
+ })
1710
+ Chat.settings.addListener('active-user-list',function(s){
1711
+ Chat.showActiveUserList(s.value);
1712
+ });
1713
+ Chat.settings.addListener('active-user-list-timestamps',function(s){
1714
+ Chat.showActiveUserTimestamps(s.value);
1715
+ });
1716
+ Chat.settings.addListener('chat-only-mode',function(s){
1717
+ Chat.chatOnlyMode(s.value);
1718
+ });
1719
+ Chat.settings.addListener('edit-compact-mode',function(s){
1720
+ Chat.e.inputLine.classList[
1721
+ s.value ? 'add' : 'remove'
1722
+ ]('compact');
1723
+ });
1724
+ Chat.settings.addListener('edit-ctrl-send',function(s){
1725
+ const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1726
+ const eInput = Chat.inputElement();
1727
+ eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
1728
+ Chat.e.btnSubmit.title = label;
1729
+ });
1730
+ const valueKludges = {
1731
+ /* Convert certain string-format values to other types... */
1732
+ "false": false,
1733
+ "true": true
1734
+ };
1735
+ Object.keys(Chat.settings.defaults).forEach(function(k){
1736
+ var v = Chat.settings.get(k,Chat);
1737
+ if(Chat===v) v = Chat.settings.defaults[k];
1738
+ if(valueKludges.hasOwnProperty(v)) v = valueKludges[v];
1739
+ Chat.settings.set(k,v)
1740
+ /* fires event listeners so that the Config area checkboxes
1741
+ get in sync */;
1742
+ });
1743
+ })();
1744
+
15881745
(function(){/*set up message preview*/
15891746
const btnPreview = Chat.e.btnPreview;
15901747
Chat.setPreviewText = function(t){
15911748
this.setCurrentView(this.e.viewPreview);
15921749
this.e.previewContent.innerHTML = t;
15931750
this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
15941751
setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1595
- this.e.inputCurrent.focus();
1752
+ this.inputFocus();
15961753
};
15971754
Chat.e.viewPreview.querySelector('#chat-preview-close').
15981755
addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
15991756
let previewPending = false;
1600
- const elemsToEnable = [
1601
- btnPreview, Chat.e.btnSubmit,
1602
- Chat.e.inputSingle, Chat.e.inputMulti];
1757
+ const elemsToEnable = [btnPreview, Chat.e.btnSubmit, Chat.e.inputField];
16031758
const submit = function(ev){
16041759
ev.preventDefault();
16051760
ev.stopPropagation();
16061761
if(previewPending) return false;
1607
- const txt = Chat.e.inputCurrent.value;
1762
+ const txt = Chat.inputValue();
16081763
if(!txt){
16091764
Chat.setPreviewText('');
16101765
previewPending = false;
16111766
return false;
16121767
}
@@ -1665,11 +1820,13 @@
16651820
if( m.mdel ){
16661821
/* A record deletion notice. */
16671822
Chat.deleteMessageElem(m.mdel);
16681823
return;
16691824
}
1670
- if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
1825
+ if(!Chat._isBatchLoading
1826
+ && (Chat.me!==m.xfrom
1827
+ || Chat.settings.getBool('alert-own-messages'))){
16711828
Chat.playNewMessageSound();
16721829
}
16731830
const row = new Chat.MessageWidget(m);
16741831
Chat.injectMessageElem(row.e.body,atEnd);
16751832
if(m.isError){
@@ -1831,13 +1988,13 @@
18311988
Chat.chatOnlyMode(true);
18321989
}
18331990
Chat.intervalTimer = setInterval(poll, 1000);
18341991
if(0){
18351992
const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1836
- document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1993
+ document.querySelectorAll('#chat-buttons-wrapper .cbutton').forEach(function(e){
18371994
e.addEventListener('click',flip, false);
18381995
});
18391996
}
18401997
setTimeout( ()=>Chat.inputFocus(), 0 );
18411998
Chat.animate.$disabled = false;
18421999
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
18432000
});
18442001
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126 inputWrapper: E1("#chat-input-area"),
127 inputLine: E1('#chat-input-line'),
128 fileSelectWrapper: E1('#chat-input-file-area'),
129 viewMessages: E1('#chat-messages-wrapper'),
130 btnSubmit: E1('#chat-message-submit'),
131 inputSingle: E1('#chat-input-single'),
132 inputMulti: E1('#chat-input-multi'),
133 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
134 inputFile: E1('#chat-input-file'),
135 contentDiv: E1('div.content'),
136 viewConfig: E1('#chat-config'),
137 viewPreview: E1('#chat-preview'),
138 previewContent: E1('#chat-preview-content'),
139 btnPreview: E1('#chat-preview-button'),
140 views: document.querySelectorAll('.chat-view'),
141 activeUserListWrapper: E1('#chat-user-list-wrapper'),
142 activeUserList: E1('#chat-user-list'),
143 btnClearFilter: E1('#chat-clear-filter')
144 },
@@ -185,56 +184,23 @@
185 taking into account single- vs multi-line input. The getter returns
186 a string and the setter returns this object. */
187 inputValue: function(){
188 const e = this.inputElement();
189 if(arguments.length){
190 e.value = arguments[0];
191 return this;
192 }
193 return e.value;
194 },
195 /** Asks the current user input field to take focus. Returns this. */
196 inputFocus: function(){
197 this.inputElement().focus();
198 return this;
199 },
200 /** Returns the current message input element. */
201 inputElement: function(){
202 return this.e.inputCurrent;
203 },
204 /** Toggles between single- and multi-line edit modes. Returns this. */
205 inputToggleSingleMulti: function(){
206 const old = this.e.inputCurrent;
207 if(this.e.inputCurrent === this.e.inputSingle){
208 this.e.inputCurrent = this.e.inputMulti;
209 this.e.inputLine.classList.remove('single-line');
210 }else{
211 this.e.inputCurrent = this.e.inputSingle;
212 this.e.inputLine.classList.add('single-line');
213 }
214 const m = this.e.viewMessages,
215 sTop = m.scrollTop,
216 mh1 = m.clientHeight;
217 D.addClass(old, 'hidden');
218 D.removeClass(this.e.inputCurrent, 'hidden');
219 const mh2 = m.clientHeight;
220 m.scrollTo(0, sTop + (mh1-mh2));
221 this.e.inputCurrent.value = old.value;
222 old.value = '';
223 return this;
224 },
225 /**
226 If passed true or no arguments, switches to multi-line mode
227 if currently in single-line mode. If passed false, switches
228 to single-line mode if currently in multi-line mode. Returns
229 this.
230 */
231 inputMultilineMode: function(yes){
232 if(!arguments.length) yes = true;
233 if(yes && this.e.inputCurrent === this.e.inputMulti) return this;
234 else if(!yes && this.e.inputCurrent === this.e.inputSingle) return this;
235 else return this.inputToggleSingleMulti();
236 },
237 /** Enables (if yes is truthy) or disables all elements in
238 * this.disableDuringAjax. */
239 enableAjaxComponents: function(yes){
240 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
409 return e ? overlapsElemView(e, this.e.viewMessages) : false;
410 },
411 settings:{
412 get: (k,dflt)=>F.storage.get(k,dflt),
413 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
414 set: (k,v)=>F.storage.set(k,v),
 
 
 
415 /* Toggles the boolean setting specified by k. Returns the
416 new value.*/
417 toggle: function(k){
418 const v = this.getBool(k);
419 this.set(k, !v);
420 return !v;
421 },
 
 
 
 
 
 
 
422 defaults:{
 
 
423 "images-inline": !!F.config.chat.imagesInline,
424 "edit-multiline": false,
425 "monospace-messages": false,
 
 
 
 
 
 
 
 
 
 
 
426 "chat-only-mode": false,
 
 
 
 
427 "audible-alert": true,
 
 
 
428 "active-user-list": false,
429 "active-user-list-timestamps": false
 
 
 
 
 
 
430 }
431 },
432 /** Plays a new-message notification sound IF the audible-alert
433 setting is true, else this is a no-op. Returns this.
434 */
@@ -437,11 +439,11 @@
437 try{
438 if(!f.audio) f.audio = new Audio(f.uri);
439 f.audio.currentTime = 0;
440 f.audio.play();
441 }catch(e){
442 console.error("Audio playblack failed.",e);
443 }
444 }
445 return this;
446 },
447 /**
@@ -466,11 +468,12 @@
466 return e;
467 }
468 this.e.views.forEach(function(E){
469 if(e!==E) D.addClass(E,'hidden');
470 });
471 this.e.currentView = D.removeClass(e,'hidden');
 
472 this.animate(this.e.currentView, 'anim-fade-in-fast');
473 return this.e.currentView;
474 },
475 /**
476 Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
585 /*Unfortante filter-specific logic*/
586 (e)=>e.classList.remove('selected')
587 );
588 return this;
589 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590 /**
591 Applies user name filter to all current messages, or clears
592 the filter if uname is falsy.
593 */
594 setUserFilter: function(uname){
@@ -634,38 +664,18 @@
634 D.addClassBriefly(e, a, 0, cb);
635 }
636 return this;
637 }
638 };
 
 
 
 
 
639 cs.animate.$disabled = true;
640 F.fetch.beforesend = ()=>cs.ajaxStart();
641 F.fetch.aftersend = ()=>cs.ajaxEnd();
642 cs.e.inputCurrent = cs.e.inputSingle;
643 /* Install default settings... */
644 Object.keys(cs.settings.defaults).forEach(function(k){
645 const v = cs.settings.get(k,cs);
646 if(cs===v) cs.settings.set(k,cs.settings.defaults[k]);
647 });
648 if(window.innerWidth<window.innerHeight){
649 /* Alignment of 'my' messages: right alignment is conventional
650 for mobile chat apps but can be difficult to read in wide
651 windows (desktop/tablet landscape mode), so we default to a
652 layout based on the apparent "orientation" of the window:
653 tall vs wide. Can be toggled via settings popup. */
654 document.body.classList.add('my-messages-right');
655 }
656 if(cs.settings.getBool('monospace-messages',false)){
657 document.body.classList.add('monospace-messages');
658 }
659 if(cs.settings.getBool('active-user-list',false)){
660 cs.e.activeUserListWrapper.classList.remove('hidden');
661 }
662 if(cs.settings.getBool('active-user-list-timestamps',false)){
663 cs.e.activeUserList.classList.add('timestamps');
664 }
665 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
666 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
667 cs.pageTitleOrig = cs.e.pageTitle.innerText;
668 const qs = (e)=>document.querySelector(e);
669 const argsToArray = function(args){
670 return Array.prototype.slice.call(args,0);
671 };
@@ -1234,11 +1244,11 @@
1234 }
1235 };
1236 return cf;
1237 })()/*MessageWidget*/;
1238
1239 const BlobXferState = (function(){/*drag/drop bits...*/
1240 /* State for paste and drag/drop */
1241 const bxs = {
1242 dropDetails: document.querySelector('#chat-drop-details'),
1243 blob: undefined,
1244 clear: function(){
@@ -1249,75 +1259,83 @@
1249 };
1250 /** Updates the paste/drop zone with details of the pasted/dropped
1251 data. The argument must be a Blob or Blob-like object (File) or
1252 it can be falsy to reset/clear that state.*/
1253 const updateDropZoneContent = function(blob){
 
1254 const dd = bxs.dropDetails;
1255 bxs.blob = blob;
1256 D.clearElement(dd);
1257 if(!blob){
1258 Chat.e.inputFile.value = '';
1259 return;
1260 }
1261 D.append(dd, "Name: ", blob.name,
1262 D.br(), "Size: ",blob.size);
1263 if(blob.type && blob.type.startsWith("image/")){
 
 
 
1264 const img = D.img();
1265 D.append(dd, D.br(), img);
1266 const reader = new FileReader();
1267 reader.onload = (e)=>img.setAttribute('src', e.target.result);
1268 reader.readAsDataURL(blob);
1269 }
1270 const btn = D.button("Cancel");
1271 D.append(dd, D.br(), btn);
1272 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1273 };
1274 Chat.e.inputFile.addEventListener('change', function(ev){
1275 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
1276 });
1277 /* Handle image paste from clipboard. TODO: figure out how we can
1278 paste non-image binary data as if it had been selected via the
1279 file selection element. */
1280 document.addEventListener('paste', function(event){
1281 const items = event.clipboardData.items,
1282 item = items[0];
1283 if(!item || !item.type) return;
1284 else if('file'===item.kind){
 
1285 updateDropZoneContent(false/*clear prev state*/);
1286 updateDropZoneContent(item.getAsFile());
1287 }
1288 }, false);
1289 /* Add help button for drag/drop/paste zone */
1290 Chat.e.inputFile.parentNode.insertBefore(
1291 F.helpButtonlets.create(
1292 Chat.e.fileSelectWrapper.querySelector('.help-buttonlet')
1293 ), Chat.e.inputFile
1294 );
1295 ////////////////////////////////////////////////////////////
1296 // File drag/drop visual notification.
1297 const dropHighlight = Chat.e.inputFile /* target zone */;
1298 const dropEvents = {
1299 drop: function(ev){
1300 D.removeClass(dropHighlight, 'dragover');
1301 },
1302 dragenter: function(ev){
1303 ev.preventDefault();
1304 ev.dataTransfer.dropEffect = "copy";
1305 D.addClass(dropHighlight, 'dragover');
1306 },
1307 dragleave: function(ev){
1308 D.removeClass(dropHighlight, 'dragover');
1309 },
1310 dragend: function(ev){
1311 D.removeClass(dropHighlight, 'dragover');
1312 }
 
 
 
1313 };
1314 Object.keys(dropEvents).forEach(
1315 (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true)
 
 
 
1316 );
1317 return bxs;
1318 })()/*drag/drop*/;
1319
1320 const tzOffsetToString = function(off){
1321 const hours = Math.round(off/60), min = Math.round(off % 30);
1322 return ''+(hours + (min ? '.5' : ''));
1323 };
@@ -1335,21 +1353,30 @@
1335 empty, this is a no-op.
1336 */
1337 Chat.submitMessage = function f(){
1338 if(!f.spaces){
1339 f.spaces = /\s+$/;
 
1340 }
1341 this.setCurrentView(this.e.viewMessages);
1342 const fd = new FormData();
1343 var msg = this.inputValue().trim();
1344 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
1345 /* Cosmetic: trim whitespace from the ends of lines to try to
1346 keep copy/paste from terminals, especially wide ones, from
1347 forcing a horizontal scrollbar on all clients. */
 
 
 
 
 
 
1348 const xmsg = msg.split('\n');
1349 xmsg.forEach(function(line,ndx){
1350 xmsg[ndx] = line.trimRight();
 
 
1351 });
1352 msg = xmsg.join('\n');
1353 }
1354 if(msg) fd.set('msg',msg);
1355 const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
1374 });
1375 BlobXferState.clear();
1376 Chat.inputValue("").inputFocus();
1377 };
1378
1379 const inputWidgetKeydown = function(ev){
1380 if(13 === ev.keyCode){
1381 if(ev.shiftKey){
1382 ev.preventDefault();
1383 ev.stopPropagation();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1384 Chat.e.btnPreview.click();
1385 return false;
1386 }else if((Chat.e.inputSingle===ev.target)
1387 || (ev.ctrlKey && Chat.e.inputMulti===ev.target)){
1388 /* ^^^ note that it is intended that both ctrl-enter and enter
1389 work for single-line input mode. */
1390 ev.preventDefault();
1391 ev.stopPropagation();
1392 Chat.submitMessage();
1393 return false;
1394 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1395 }
1396 };
1397 Chat.e.inputSingle
1398 .addEventListener('keydown', inputWidgetKeydown, false);
1399 Chat.e.inputMulti
1400 .addEventListener('keydown', inputWidgetKeydown, false);
1401 Chat.e.btnSubmit.addEventListener('click',(e)=>{
1402 e.preventDefault();
1403 Chat.submitMessage();
1404 return false;
1405 });
 
 
1406
1407 (function(){/*Set up #chat-settings-button */
1408 const settingsButton = document.querySelector('#chat-settings-button');
 
 
 
 
 
 
 
 
 
1409 const optionsMenu = E1('#chat-config-options');
1410 const cbToggle = function(ev){
1411 ev.preventDefault();
1412 ev.stopPropagation();
1413 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
1420 /** Internal acrobatics to allow certain settings toggles to access
1421 related toggles. */
1422 const namedOptions = {
1423 activeUsers:{
1424 label: "Show active users list",
1425 boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1426 persistentSetting: 'active-user-list',
1427 callback: function(){
1428 D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1429 D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1430 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1431 /* When hiding this element, undo user filtering */
1432 if(Chat.filter.current === Chat.filter.user){
1433 Chat.setUserFilter(false);
1434 }
1435 /*Ideally we'd scroll the final message into view
1436 now, but because viewMessages is currently hidden behind
1437 viewConfig, scrolling is a no-op. */
1438 Chat.scrollMessagesTo(1);
1439 }else{
1440 Chat.updateActiveUserList();
1441 Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1442 }
1443 }
1444 }
1445 };
1446 if(1){
1447 /* Per user request, toggle the list of users on and off if the
1448 legend element is tapped. */
@@ -1454,65 +1515,82 @@
1454 if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1455 Chat.animate(optAu.theList,'anim-flip-v');
1456 }
1457 }, false);
1458 }/*namedOptions.activeUsers additional setup*/
1459 /* Settings menu entries... Remember that they will be rendered in
1460 reverse order and the most frequently-needed ones "should"
1461 (arguably) be closer to the start of this list so that they
1462 will be rendered within easier reach of the settings button. */
1463 const settingsOps = [{
1464 label: "Multi-line input",
1465 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1466 persistentSetting: 'edit-multiline',
1467 callback: function(){
1468 Chat.inputToggleSingleMulti();
1469 }
1470 },{
1471 label: "Left-align my posts",
1472 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1473 callback: function f(){
1474 document.body.classList.toggle('my-messages-right');
1475 }
1476 },{
1477 label: "Show images inline",
1478 boolValue: ()=>Chat.settings.getBool('images-inline'),
1479 callback: function(){
1480 const v = Chat.settings.toggle('images-inline');
1481 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1482 }
1483 },{
1484 label: "Timestamps in active users list",
1485 boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1486 persistentSetting: 'active-user-list-timestamps',
1487 callback: function(){
1488 D.toggleClass(Chat.e.activeUserList,'timestamps');
1489 /* If the timestamp option is activated but
1490 namedOptions.activeUsers is not currently checked then
1491 toggle that option on as well. */
1492 if(Chat.e.activeUserList.classList.contains('timestamps')
1493 && !namedOptions.activeUsers.boolValue()){
1494 namedOptions.activeUsers.checkbox.checked = true;
1495 namedOptions.activeUsers.callback();
1496 Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1497 }
1498 }
1499 },
1500 namedOptions.activeUsers,{
1501 label: "Monospace message font",
1502 boolValue: ()=>document.body.classList.contains('monospace-messages'),
1503 persistentSetting: 'monospace-messages',
1504 callback: function(){
1505 document.body.classList.toggle('monospace-messages');
 
 
 
 
 
 
 
 
 
 
 
 
1506 }
1507 },{
1508 label: "Chat-only mode",
1509 boolValue: ()=>Chat.isChatOnlyMode(),
1510 persistentSetting: 'chat-only-mode',
1511 callback: function(){
1512 Chat.toggleChatOnlyMode();
1513 }
 
 
 
 
 
1514 }];
1515
1516 /** Set up selection list of notification sounds. */
1517 if(1){
1518 const selectSound = D.select();
@@ -1522,91 +1600,168 @@
1522 if(true===Chat.settings.getBool('audible-alert')){
1523 /* This setting used to be a plain bool. If we encounter
1524 such a setting, take the first sound in the list. */
1525 selectSound.selectedIndex = firstSoundIndex;
1526 }else{
1527 selectSound.value = Chat.settings.get('audible-alert','');
1528 if(selectSound.selectedIndex<0){
1529 /* Missing file - removed after this setting was
1530 applied. Fall back to the first sound in the list. */
1531 selectSound.selectedIndex = firstSoundIndex;
1532 }
1533 }
1534 Chat.setNewMessageSound(selectSound.value);
1535 settingsOps.push({
1536 label: "Audio alert",
1537 select: selectSound,
1538 callback: function(ev){
1539 const v = ev.target.value;
1540 Chat.setNewMessageSound(v);
1541 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1542 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1543 }
1544 });
1545 }/*audio notification config*/
 
 
 
 
 
 
 
1546 /**
1547 Build UI for config options...
1548 */
1549 settingsOps.forEach(function f(op){
1550 const line = D.addClass(D.div(), 'menu-entry');
1551 const btn = D.append(
1552 D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
1553 op.label);
1554 const callback = function(ev){
1555 op.callback(ev);
1556 if(op.persistentSetting){
1557 Chat.settings.set(op.persistentSetting, op.boolValue());
1558 }
1559 };
1560 if(op.hasOwnProperty('select')){
1561 D.append(line, btn, op.select);
1562 op.select.addEventListener('change', callback, false);
 
 
 
 
 
1563 }else if(op.hasOwnProperty('boolValue')){
1564 if(undefined === f.$id) f.$id = 0;
1565 ++f.$id;
 
 
 
 
 
1566 const check = op.checkbox
1567 = D.attr(D.checkbox(1, op.boolValue()),
1568 'aria-label', op.label);
1569 const id = 'cfgopt'+f.$id;
1570 if(op.boolValue()) check.checked = true;
 
1571 D.attr(check, 'id', id);
1572 D.attr(btn, 'for', id);
1573 D.append(line, check);
1574 check.addEventListener('change', callback);
1575 D.append(line, btn);
 
 
 
1576 }else{
1577 line.addEventListener('click', callback);
1578 D.append(line, btn);
 
 
1579 }
1580 D.append(optionsMenu, line);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1581 });
1582 if(0 && settingsOps.selectSound){
1583 D.append(optionsMenu, settingsOps.selectSound);
1584 }
1585 //settingsButton.click()/*for for development*/;
1586 })()/*#chat-settings-button setup*/;
1587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1588 (function(){/*set up message preview*/
1589 const btnPreview = Chat.e.btnPreview;
1590 Chat.setPreviewText = function(t){
1591 this.setCurrentView(this.e.viewPreview);
1592 this.e.previewContent.innerHTML = t;
1593 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1594 setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1595 this.e.inputCurrent.focus();
1596 };
1597 Chat.e.viewPreview.querySelector('#chat-preview-close').
1598 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
1599 let previewPending = false;
1600 const elemsToEnable = [
1601 btnPreview, Chat.e.btnSubmit,
1602 Chat.e.inputSingle, Chat.e.inputMulti];
1603 const submit = function(ev){
1604 ev.preventDefault();
1605 ev.stopPropagation();
1606 if(previewPending) return false;
1607 const txt = Chat.e.inputCurrent.value;
1608 if(!txt){
1609 Chat.setPreviewText('');
1610 previewPending = false;
1611 return false;
1612 }
@@ -1665,11 +1820,13 @@
1665 if( m.mdel ){
1666 /* A record deletion notice. */
1667 Chat.deleteMessageElem(m.mdel);
1668 return;
1669 }
1670 if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){
 
 
1671 Chat.playNewMessageSound();
1672 }
1673 const row = new Chat.MessageWidget(m);
1674 Chat.injectMessageElem(row.e.body,atEnd);
1675 if(m.isError){
@@ -1831,13 +1988,13 @@
1831 Chat.chatOnlyMode(true);
1832 }
1833 Chat.intervalTimer = setInterval(poll, 1000);
1834 if(0){
1835 const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1836 document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1837 e.addEventListener('click',flip, false);
1838 });
1839 }
1840 setTimeout( ()=>Chat.inputFocus(), 0 );
1841 Chat.animate.$disabled = false;
1842 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
1843 });
1844
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -125,20 +125,19 @@
125 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
126 inputWrapper: E1("#chat-input-area"),
127 inputLine: E1('#chat-input-line'),
128 fileSelectWrapper: E1('#chat-input-file-area'),
129 viewMessages: E1('#chat-messages-wrapper'),
130 btnSubmit: E1('#chat-button-submit'),
131 btnAttach: E1('#chat-button-attach'),
132 inputField: E1('#chat-input-field'),
 
133 inputFile: E1('#chat-input-file'),
134 contentDiv: E1('div.content'),
135 viewConfig: E1('#chat-config'),
136 viewPreview: E1('#chat-preview'),
137 previewContent: E1('#chat-preview-content'),
138 btnPreview: E1('#chat-button-preview'),
139 views: document.querySelectorAll('.chat-view'),
140 activeUserListWrapper: E1('#chat-user-list-wrapper'),
141 activeUserList: E1('#chat-user-list'),
142 btnClearFilter: E1('#chat-clear-filter')
143 },
@@ -185,56 +184,23 @@
184 taking into account single- vs multi-line input. The getter returns
185 a string and the setter returns this object. */
186 inputValue: function(){
187 const e = this.inputElement();
188 if(arguments.length){
189 e.innerText = arguments[0];
190 return this;
191 }
192 return e.innerText;
193 },
194 /** Asks the current user input field to take focus. Returns this. */
195 inputFocus: function(){
196 this.inputElement().focus();
197 return this;
198 },
199 /** Returns the current message input element. */
200 inputElement: function(){
201 return this.e.inputField;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202 },
203 /** Enables (if yes is truthy) or disables all elements in
204 * this.disableDuringAjax. */
205 enableAjaxComponents: function(yes){
206 D[yes ? 'enable' : 'disable'](this.disableDuringAjax);
@@ -409,26 +375,62 @@
375 return e ? overlapsElemView(e, this.e.viewMessages) : false;
376 },
377 settings:{
378 get: (k,dflt)=>F.storage.get(k,dflt),
379 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
380 set: function(k,v){
381 F.storage.set(k,v);
382 F.page.dispatchEvent('chat-setting',{key: k, value: v});
383 },
384 /* Toggles the boolean setting specified by k. Returns the
385 new value.*/
386 toggle: function(k){
387 const v = this.getBool(k);
388 this.set(k, !v);
389 return !v;
390 },
391 addListener: function(setting, f){
392 F.page.addEventListener('chat-setting', function(ev){
393 if(ev.detail.key===setting) f(ev.detail);
394 }, false);
395 },
396 /* Default values of settings. These are used for intializing
397 the setting event listeners and config view UI. */
398 defaults:{
399 /* When on, inbound images are displayed inlined, else as a
400 link to download the image. */
401 "images-inline": !!F.config.chat.imagesInline,
402 /* When on, ctrl-enter sends messages, else enter and
403 ctrl-enter both send them. */
404 "edit-ctrl-send": false,
405 /* When on, the edit field starts as a single line and
406 expands as the user types, and the relevant buttons are
407 laid out in a compact form. When off, the edit field and
408 buttons are larger. */
409 "edit-compact-mode": true,
410 /* When on, sets the font-family on messages and the edit
411 field to monospace. */
412 "monospace-messages": true,
413 /* When on, non-chat UI elements (page header/footer) are
414 hidden */
415 "chat-only-mode": false,
416 /* When set to a URI, it is assumed to be an audio file,
417 which gets played when new messages arrive. When true,
418 the first entry in the audio file selection list will be
419 used. */
420 "audible-alert": true,
421 /* When on, show the list of "active" users - those from
422 whom we have messages in the currently-loaded history
423 (noting that deletions are also messages). */
424 "active-user-list": false,
425 /* When on, the [active-user-list] setting includes the
426 timestamp of each user's most recent message. */
427 "active-user-list-timestamps": false,
428 /* When on, the [audible-alert] is played for one's own
429 messages, else it is only played for other users'
430 messages. */
431 "alert-own-messages": false
432 }
433 },
434 /** Plays a new-message notification sound IF the audible-alert
435 setting is true, else this is a no-op. Returns this.
436 */
@@ -437,11 +439,11 @@
439 try{
440 if(!f.audio) f.audio = new Audio(f.uri);
441 f.audio.currentTime = 0;
442 f.audio.play();
443 }catch(e){
444 console.error("Audio playblack failed.", f.uri, e);
445 }
446 }
447 return this;
448 },
449 /**
@@ -466,11 +468,12 @@
468 return e;
469 }
470 this.e.views.forEach(function(E){
471 if(e!==E) D.addClass(E,'hidden');
472 });
473 this.e.currentView = e;
474 D.removeClass(e,'hidden');
475 this.animate(this.e.currentView, 'anim-fade-in-fast');
476 return this.e.currentView;
477 },
478 /**
479 Updates the "active user list" view if we are not currently
@@ -585,10 +588,37 @@
588 /*Unfortante filter-specific logic*/
589 (e)=>e.classList.remove('selected')
590 );
591 return this;
592 },
593 /**
594 Show or hide the active user list. Returns this object.
595 */
596 showActiveUserList: function(yes){
597 if(0===arguments.length) yes = true;
598 this.e.activeUserListWrapper.classList[
599 yes ? 'remove' : 'add'
600 ]('hidden');
601 D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
602 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
603 /* When hiding this element, undo all filtering */
604 Chat.setUserFilter(false);
605 /*Ideally we'd scroll the final message into view
606 now, but because viewMessages is currently hidden behind
607 viewConfig, scrolling is a no-op. */
608 Chat.scrollMessagesTo(1);
609 }else{
610 Chat.updateActiveUserList();
611 Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
612 }
613 return this;
614 },
615 showActiveUserTimestamps: function(yes){
616 if(0===arguments.length) yes = true;
617 this.e.activeUserList.classList[yes ? 'add' : 'remove']('timestamps');
618 return this;
619 },
620 /**
621 Applies user name filter to all current messages, or clears
622 the filter if uname is falsy.
623 */
624 setUserFilter: function(uname){
@@ -634,38 +664,18 @@
664 D.addClassBriefly(e, a, 0, cb);
665 }
666 return this;
667 }
668 };
669 if(!D.attr(cs.e.inputField,'contenteditable','plaintext-only').isContentEditable){
670 /* Only the Chrome family supports contenteditable=plaintext-only,
671 but Chrome is the only engine for which we need this flag: */
672 D.attr(cs.e.inputField,'contenteditable','true');
673 }
674 cs.animate.$disabled = true;
675 F.fetch.beforesend = ()=>cs.ajaxStart();
676 F.fetch.aftersend = ()=>cs.ajaxEnd();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677 cs.pageTitleOrig = cs.e.pageTitle.innerText;
678 const qs = (e)=>document.querySelector(e);
679 const argsToArray = function(args){
680 return Array.prototype.slice.call(args,0);
681 };
@@ -1234,11 +1244,11 @@
1244 }
1245 };
1246 return cf;
1247 })()/*MessageWidget*/;
1248
1249 const BlobXferState = (function(){
1250 /* State for paste and drag/drop */
1251 const bxs = {
1252 dropDetails: document.querySelector('#chat-drop-details'),
1253 blob: undefined,
1254 clear: function(){
@@ -1249,75 +1259,83 @@
1259 };
1260 /** Updates the paste/drop zone with details of the pasted/dropped
1261 data. The argument must be a Blob or Blob-like object (File) or
1262 it can be falsy to reset/clear that state.*/
1263 const updateDropZoneContent = function(blob){
1264 //console.debug("updateDropZoneContent()",blob);
1265 const dd = bxs.dropDetails;
1266 bxs.blob = blob;
1267 D.clearElement(dd);
1268 if(!blob){
1269 Chat.e.inputFile.value = '';
1270 return;
1271 }
1272 D.append(dd, "Attached: ", blob.name,
1273 D.br(), "Size: ",blob.size);
1274 const btn = D.button("Cancel");
1275 D.append(dd, D.br(), btn);
1276 btn.addEventListener('click', ()=>updateDropZoneContent(), false);
1277 if(blob.type && (blob.type.startsWith("image/") || blob.type==='BITMAP')){
1278 const img = D.img();
1279 D.append(dd, D.br(), img);
1280 const reader = new FileReader();
1281 reader.onload = (e)=>img.setAttribute('src', e.target.result);
1282 reader.readAsDataURL(blob);
1283 }
 
 
 
1284 };
1285 Chat.e.inputFile.addEventListener('change', function(ev){
1286 updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
1287 });
1288 /* Handle image paste from clipboard. TODO: figure out how we can
1289 paste non-image binary data as if it had been selected via the
1290 file selection element. */
1291 const pasteListener = function(event){
1292 const items = event.clipboardData.items,
1293 item = items[0];
1294 //console.debug("paste event",event.target,item,event);
1295 //console.debug("paste event item",item);
1296 if(item && item.type && ('file'===item.kind || 'BITMAP'===item.type)){
1297 updateDropZoneContent(false/*clear prev state*/);
1298 updateDropZoneContent(item.getAsFile());
1299 event.stopPropagation();
1300 event.preventDefault(true);
1301 return false;
1302 }
1303 /* else continue propagating */
1304 };
1305 document.addEventListener('paste', pasteListener, true);
1306 if(0){
1307 const onPastePlainText = function(ev){
1308 var pastedText = undefined;
1309 if (window.clipboardData && window.clipboardData.getData) { // IE
1310 pastedText = window.clipboardData.getData('Text');
1311 }else if (ev.clipboardData && ev.clipboardData.getData) {
1312 pastedText = ev.clipboardData.getData('text/plain');
1313 }
1314 ev.target.textContent += pastedText;
1315 ev.preventDefault();
1316 return false;
1317 };
1318 Chat.e.inputField.addEventListener('paste', onPastePlainText, false);
1319 }
1320 const noDragDropEvents = function(ev){
1321 /* contenteditable tries to do its own thing with dropped data,
1322 which is not compatible with how we use it, so... */
1323 ev.dataTransfer.effectAllowed = 'none';
1324 ev.dataTransfer.dropEffect = 'none';
1325 ev.preventDefault();
1326 ev.stopPropagation();
1327 return false;
1328 };
1329
1330 ['drop','dragenter','dragleave','dragend'].forEach(
1331 (k)=>{
1332 Chat.inputElement().addEventListener(k, noDragDropEvents, false);
1333 }
1334 );
1335 return bxs;
1336 })()/*drag/drop/paste*/;
1337
1338 const tzOffsetToString = function(off){
1339 const hours = Math.round(off/60), min = Math.round(off % 30);
1340 return ''+(hours + (min ? '.5' : ''));
1341 };
@@ -1335,21 +1353,30 @@
1353 empty, this is a no-op.
1354 */
1355 Chat.submitMessage = function f(){
1356 if(!f.spaces){
1357 f.spaces = /\s+$/;
1358 f.markdownContinuation = /\\\s+$/;
1359 }
1360 this.setCurrentView(this.e.viewMessages);
1361 const fd = new FormData();
1362 var msg = this.inputValue().trim();
1363 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
1364 /* Cosmetic: trim whitespace from the ends of lines to try to
1365 keep copy/paste from terminals, especially wide ones, from
1366 forcing a horizontal scrollbar on all clients. This breaks
1367 markdown's use of blackslash-space-space for paragraph
1368 continuation, but *not* doing this affects all clients every
1369 time someone pastes in console copy/paste from an affected
1370 platform. We seem to have narrowed to the console pasting
1371 problem to users of tmux. Most consoles don't behave
1372 that way. */
1373 const xmsg = msg.split('\n');
1374 xmsg.forEach(function(line,ndx){
1375 if(!f.markdownContinuation.test(line)){
1376 xmsg[ndx] = line.trimRight();
1377 }
1378 });
1379 msg = xmsg.join('\n');
1380 }
1381 if(msg) fd.set('msg',msg);
1382 const file = BlobXferState.blob || this.e.inputFile.files[0];
@@ -1374,40 +1401,91 @@
1401 });
1402 BlobXferState.clear();
1403 Chat.inputValue("").inputFocus();
1404 };
1405
1406 const inputWidgetKeydown = function f(ev){
1407 if(!f.$toggleCtrl){
1408 f.$toggleCtrl = function(currentMode){
1409 currentMode = !currentMode;
1410 Chat.settings.set('edit-ctrl-send', currentMode);
1411 };
1412 f.$toggleCompact = function(currentMode){
1413 currentMode = !currentMode;
1414 Chat.settings.set('edit-compact-mode', currentMode);
1415 };
1416 }
1417 if(13 !== ev.keyCode) return;
1418 const text = Chat.inputValue().trim();
1419 const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
1420 //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
1421 if(ev.shiftKey){
1422 const compactMode = Chat.settings.getBool('edit-compact-mode', false);
1423 ev.preventDefault();
1424 ev.stopPropagation();
1425 /* Shift-enter will run preview mode UNLESS preview mode is
1426 active AND the input field is empty, in which case it will
1427 switch back to message view. */
1428 if(Chat.e.currentView===Chat.e.viewPreview && !text){
1429 Chat.setCurrentView(Chat.e.viewMessages);
1430 }else if(!text){
1431 f.$toggleCompact(compactMode);
1432 }else{
1433 Chat.e.btnPreview.click();
1434 }
1435 return false;
1436 }
1437 if(ev.ctrlKey && !text && !BlobXferState.blob){
1438 /* Ctrl-enter on empty input field(s) toggles Enter/Ctrl-enter mode */
1439 ev.preventDefault();
1440 ev.stopPropagation();
1441 f.$toggleCtrl(ctrlMode);
1442 return false;
1443 }
1444 if(!ctrlMode && ev.ctrlKey && text){
1445 //console.debug("!ctrlMode && ev.ctrlKey && text.");
1446 /* Ctrl-enter in Enter-sends mode SHOULD, with this logic add a
1447 newline, but that is not happening, for unknown reasons
1448 (possibly related to this element being a conteneditable DIV
1449 instead of a textarea). Forcibly appending a newline do the
1450 input area does not work, also for unknown reasons, and would
1451 only be suitable when we're at the end of the input.
1452
1453 Strangely, this approach DOES work for shift-enter, but we
1454 need shift-enter as a hotkey for preview mode.
1455 */
1456 //return;
1457 // return here "should" cause newline to be added, but that doesn't work
1458 }
1459 if((!ctrlMode && !ev.ctrlKey) || (ev.ctrlKey/* && ctrlMode*/)){
1460 /* Ship it! */
1461 ev.preventDefault();
1462 ev.stopPropagation();
1463 Chat.submitMessage();
1464 return false;
1465 }
1466 };
1467 Chat.e.inputField.addEventListener('keydown', inputWidgetKeydown, false);
 
 
 
1468 Chat.e.btnSubmit.addEventListener('click',(e)=>{
1469 e.preventDefault();
1470 Chat.submitMessage();
1471 return false;
1472 });
1473 Chat.e.btnAttach.addEventListener(
1474 'click', ()=>Chat.e.inputFile.click(), false);
1475
1476 (function(){/*Set up #chat-button-settings and related bits */
1477 if(window.innerWidth<window.innerHeight){
1478 // Must be set up before config view is...
1479 /* Alignment of 'my' messages: right alignment is conventional
1480 for mobile chat apps but can be difficult to read in wide
1481 windows (desktop/tablet landscape mode), so we default to a
1482 layout based on the apparent "orientation" of the window:
1483 tall vs wide. Can be toggled via settings. */
1484 document.body.classList.add('my-messages-right');
1485 }
1486 const settingsButton = document.querySelector('#chat-button-settings');
1487 const optionsMenu = E1('#chat-config-options');
1488 const cbToggle = function(ev){
1489 ev.preventDefault();
1490 ev.stopPropagation();
1491 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
@@ -1420,29 +1498,12 @@
1498 /** Internal acrobatics to allow certain settings toggles to access
1499 related toggles. */
1500 const namedOptions = {
1501 activeUsers:{
1502 label: "Show active users list",
1503 hint: "List users who have messages in the currently-loaded chat history.",
1504 boolValue: 'active-user-list'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1505 }
1506 };
1507 if(1){
1508 /* Per user request, toggle the list of users on and off if the
1509 legend element is tapped. */
@@ -1454,65 +1515,82 @@
1515 if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1516 Chat.animate(optAu.theList,'anim-flip-v');
1517 }
1518 }, false);
1519 }/*namedOptions.activeUsers additional setup*/
1520 /**
1521 Settings options structure: an array of Objects with the
1522 following properties:
1523
1524 label: string for the UI
1525
1526 boolValue: string (name of Chat.settings setting) or a
1527 function which returns true or false. Gets mapped to
1528 a checkbox.
1529
1530 select: SELECT element (instead of boolValue)
1531
1532 callback: optional handler to call after setting is modified.
1533 It gets passed the setting object: {key:string, value:something}.
1534
1535 If a setting has a boolValue set, that gets transformed into a
1536 checkbox which toggles the given persistent setting (if
1537 boolValue is a string) AND listens for changes to that setting
1538 fired via Chat.settings.set() so that the checkbox can stay in
1539 sync with external changes to that setting. Various Chat UI
1540 elements stay in sync with the config UI via those settings
1541 events.
1542 */
1543 const settingsOps = [{
1544 label: "Ctrl-enter to Send",
1545 hint: [
1546 "When on, only Ctrl-Enter will send messages and Enter adds ",
1547 "blank lines. ",
1548 "When off, both Enter and Ctrl-Enter send. ",
1549 "When the input field has focus, is empty, and preview ",
1550 "mode is NOT active then Ctrl-Enter toggles this setting."
1551 ].join(''),
1552 boolValue: 'edit-ctrl-send'
1553 },{
1554 label: "Compact mode",
1555 hint: [
1556 "Toggle between a space-saving or more spacious writing area. ",
1557 "When the input field has focus, is empty, and preview mode ",
1558 "is NOT active then Shift-Enter toggles this setting."
1559 ].join(''),
1560 boolValue: 'edit-compact-mode'
1561 },{
1562 label: "Left-align my posts",
1563 hint: "Default alignment of your own messages is selected "
1564 +"based window width/height relationship.",
1565 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1566 callback: function f(){
1567 document.body.classList[
1568 this.checkbox.checked ? 'remove' : 'add'
1569 ]('my-messages-right');
1570 }
1571 },{
1572 label: "Monospace message font",
1573 hint: "Use monospace font for message text?",
1574 boolValue: 'monospace-messages',
1575 callback: function(setting){
1576 document.body.classList[
1577 setting.value ? 'add' : 'remove'
1578 ]('monospace-messages');
1579 }
1580 },{
1581 label: "Chat-only mode",
1582 hint: "Toggle the page between normal fossil view and chat-only view.",
1583 boolValue: 'chat-only-mode'
1584 },{
1585 label: "Show images inline",
1586 hint: "Whether to show images inline or as a hyperlink.",
1587 boolValue: 'images-inline'
1588 },namedOptions.activeUsers,{
1589 label: "Timestamps in active users list",
1590 hint: "Whether to show last-message timestamps.",
1591 boolValue: 'active-user-list-timestamps'
1592 }];
1593
1594 /** Set up selection list of notification sounds. */
1595 if(1){
1596 const selectSound = D.select();
@@ -1522,91 +1600,168 @@
1600 if(true===Chat.settings.getBool('audible-alert')){
1601 /* This setting used to be a plain bool. If we encounter
1602 such a setting, take the first sound in the list. */
1603 selectSound.selectedIndex = firstSoundIndex;
1604 }else{
1605 selectSound.value = Chat.settings.get('audible-alert','<none>');
1606 if(selectSound.selectedIndex<0){
1607 /* Missing file - removed after this setting was
1608 applied. Fall back to the first sound in the list. */
1609 selectSound.selectedIndex = firstSoundIndex;
1610 }
1611 }
1612 Chat.setNewMessageSound(selectSound.value);
1613 settingsOps.push({
1614 hint: "Audio alert. How to enable audio playback is browser-specific!",
1615 select: selectSound,
1616 callback: function(ev){
1617 const v = ev.target.value;
1618 Chat.setNewMessageSound(v);
1619 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1620 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1621 }
1622 });
1623 }/*audio notification config*/
1624 settingsOps.push({
1625 label: "Play notification for your own messages.",
1626 hint: "When enabled, the audio notification will be played for all messages, "+
1627 "including your own. When disabled only messages from other users "+
1628 "will trigger a notification.",
1629 boolValue: 'alert-own-messages'
1630 });
1631 /**
1632 Build UI for config options...
1633 */
1634 settingsOps.forEach(function f(op){
1635 const line = D.addClass(D.div(), 'menu-entry');
1636 const label = op.label
1637 ? D.append(D.label(),op.label) : undefined;
1638 const labelWrapper = D.addClass(D.div(), 'label-wrapper');
1639 var hint;
1640 const col0 = D.span();
1641 if(op.hint){
1642 hint = D.append(D.addClass(D.span(),'hint'),op.hint);
1643 }
 
1644 if(op.hasOwnProperty('select')){
1645 D.append(line, col0, labelWrapper);
1646 D.append(labelWrapper, op.select);
1647 if(hint) D.append(labelWrapper, hint);
1648 if(label) D.append(col0, label);
1649 if(op.callback){
1650 op.select.addEventListener('change', (ev)=>op.callback(ev), false);
1651 }
1652 }else if(op.hasOwnProperty('boolValue')){
1653 if(undefined === f.$id) f.$id = 0;
1654 ++f.$id;
1655 if('string' ===typeof op.boolValue){
1656 const key = op.boolValue;
1657 op.boolValue = ()=>Chat.settings.getBool(key);
1658 op.persistentSetting = key;
1659 }
1660 const check = op.checkbox
1661 = D.attr(D.checkbox(1, op.boolValue()),
1662 'aria-label', op.label);
1663 const id = 'cfgopt'+f.$id;
1664 check.checked = op.boolValue();
1665 op.checkbox = check;
1666 D.attr(check, 'id', id);
1667 D.append(line, col0, labelWrapper);
1668 D.append(col0, check);
1669 if(label){
1670 D.attr(label, 'for', id);
1671 D.append(labelWrapper, label);
1672 }
1673 if(hint) D.append(labelWrapper, hint);
1674 }else{
1675 line.addEventListener('click', callback);
1676 D.append(line, col0, labelWrapper);
1677 if(label) D.append(labelWrapper, label);
1678 if(hint) D.append(labelWrapper, hint);
1679 }
1680 D.append(optionsMenu, line);
1681 if(op.persistentSetting){
1682 Chat.settings.addListener(
1683 op.persistentSetting,
1684 function(setting){
1685 if(op.checkbox) op.checkbox.checked = !!setting.value;
1686 else if(op.select) op.select.value = setting.value;
1687 if(op.callback) op.callback(setting);
1688 }
1689 );
1690 if(op.checkbox){
1691 op.checkbox.addEventListener(
1692 'change', function(){
1693 Chat.settings.set(op.persistentSetting, op.checkbox.checked)
1694 }, false);
1695 }
1696 }else if(op.callback && op.checkbox){
1697 op.checkbox.addEventListener('change', (ev)=>op.callback(ev), false);
1698 }
1699 });
1700 })()/*#chat-button-settings setup*/;
 
 
 
 
1701
1702 (function(){
1703 /* Install default settings... must come after
1704 chat-button-settings setup so that the listeners which that
1705 installs are notified via the properties getting initialized
1706 here. */
1707 Chat.settings.addListener('monospace-messages',function(s){
1708 document.body.classList[s.value ? 'add' : 'remove']('monospace-messages');
1709 })
1710 Chat.settings.addListener('active-user-list',function(s){
1711 Chat.showActiveUserList(s.value);
1712 });
1713 Chat.settings.addListener('active-user-list-timestamps',function(s){
1714 Chat.showActiveUserTimestamps(s.value);
1715 });
1716 Chat.settings.addListener('chat-only-mode',function(s){
1717 Chat.chatOnlyMode(s.value);
1718 });
1719 Chat.settings.addListener('edit-compact-mode',function(s){
1720 Chat.e.inputLine.classList[
1721 s.value ? 'add' : 'remove'
1722 ]('compact');
1723 });
1724 Chat.settings.addListener('edit-ctrl-send',function(s){
1725 const label = (s.value ? "Ctrl-" : "")+"Enter submits messages.";
1726 const eInput = Chat.inputElement();
1727 eInput.dataset.placeholder = eInput.dataset.placeholder0 + " " +label;
1728 Chat.e.btnSubmit.title = label;
1729 });
1730 const valueKludges = {
1731 /* Convert certain string-format values to other types... */
1732 "false": false,
1733 "true": true
1734 };
1735 Object.keys(Chat.settings.defaults).forEach(function(k){
1736 var v = Chat.settings.get(k,Chat);
1737 if(Chat===v) v = Chat.settings.defaults[k];
1738 if(valueKludges.hasOwnProperty(v)) v = valueKludges[v];
1739 Chat.settings.set(k,v)
1740 /* fires event listeners so that the Config area checkboxes
1741 get in sync */;
1742 });
1743 })();
1744
1745 (function(){/*set up message preview*/
1746 const btnPreview = Chat.e.btnPreview;
1747 Chat.setPreviewText = function(t){
1748 this.setCurrentView(this.e.viewPreview);
1749 this.e.previewContent.innerHTML = t;
1750 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1751 setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
1752 this.inputFocus();
1753 };
1754 Chat.e.viewPreview.querySelector('#chat-preview-close').
1755 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
1756 let previewPending = false;
1757 const elemsToEnable = [btnPreview, Chat.e.btnSubmit, Chat.e.inputField];
 
 
1758 const submit = function(ev){
1759 ev.preventDefault();
1760 ev.stopPropagation();
1761 if(previewPending) return false;
1762 const txt = Chat.inputValue();
1763 if(!txt){
1764 Chat.setPreviewText('');
1765 previewPending = false;
1766 return false;
1767 }
@@ -1665,11 +1820,13 @@
1820 if( m.mdel ){
1821 /* A record deletion notice. */
1822 Chat.deleteMessageElem(m.mdel);
1823 return;
1824 }
1825 if(!Chat._isBatchLoading
1826 && (Chat.me!==m.xfrom
1827 || Chat.settings.getBool('alert-own-messages'))){
1828 Chat.playNewMessageSound();
1829 }
1830 const row = new Chat.MessageWidget(m);
1831 Chat.injectMessageElem(row.e.body,atEnd);
1832 if(m.isError){
@@ -1831,13 +1988,13 @@
1988 Chat.chatOnlyMode(true);
1989 }
1990 Chat.intervalTimer = setInterval(poll, 1000);
1991 if(0){
1992 const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1993 document.querySelectorAll('#chat-buttons-wrapper .cbutton').forEach(function(e){
1994 e.addEventListener('click',flip, false);
1995 });
1996 }
1997 setTimeout( ()=>Chat.inputFocus(), 0 );
1998 Chat.animate.$disabled = false;
1999 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
2000 });
2001
--- src/setup.c
+++ src/setup.c
@@ -682,13 +682,10 @@
682682
@ <p><form action="%R/setup_login_group" method="post"><div>
683683
login_insert_csrf_secret();
684684
@ To leave this login group press
685685
@ <input type="submit" value="Leave Login Group" name="leave">
686686
@ </form></p>
687
- @ <br />For best results, use the same number of <a href="setup_access#ipt">
688
- @ IP octets</a> in the login cookie across all repositories in the
689
- @ same Login Group.
690687
@ <hr /><h2>Implementation Details</h2>
691688
@ <p>The following are fields from the CONFIG table related to login-groups,
692689
@ provided here for instructional and debugging purposes:</p>
693690
@ <table border='1' class='sortable' data-column-types='ttt' \
694691
@ data-init-sort='1'>
695692
--- src/setup.c
+++ src/setup.c
@@ -682,13 +682,10 @@
682 @ <p><form action="%R/setup_login_group" method="post"><div>
683 login_insert_csrf_secret();
684 @ To leave this login group press
685 @ <input type="submit" value="Leave Login Group" name="leave">
686 @ </form></p>
687 @ <br />For best results, use the same number of <a href="setup_access#ipt">
688 @ IP octets</a> in the login cookie across all repositories in the
689 @ same Login Group.
690 @ <hr /><h2>Implementation Details</h2>
691 @ <p>The following are fields from the CONFIG table related to login-groups,
692 @ provided here for instructional and debugging purposes:</p>
693 @ <table border='1' class='sortable' data-column-types='ttt' \
694 @ data-init-sort='1'>
695
--- src/setup.c
+++ src/setup.c
@@ -682,13 +682,10 @@
682 @ <p><form action="%R/setup_login_group" method="post"><div>
683 login_insert_csrf_secret();
684 @ To leave this login group press
685 @ <input type="submit" value="Leave Login Group" name="leave">
686 @ </form></p>
 
 
 
687 @ <hr /><h2>Implementation Details</h2>
688 @ <p>The following are fields from the CONFIG table related to login-groups,
689 @ provided here for instructional and debugging purposes:</p>
690 @ <table border='1' class='sortable' data-column-types='ttt' \
691 @ data-init-sort='1'>
692
+33 -21
--- src/shell.c
+++ src/shell.c
@@ -5398,11 +5398,15 @@
53985398
e--;
53995399
}
54005400
e += 1075;
54015401
if( e<=0 ){
54025402
/* Subnormal */
5403
- m >>= 1-e;
5403
+ if( 1-e >= 64 ){
5404
+ m = 0;
5405
+ }else{
5406
+ m >>= 1-e;
5407
+ }
54045408
e = 0;
54055409
}else if( e>0x7ff ){
54065410
e = 0x7ff;
54075411
}
54085412
a = m & ((((sqlite3_int64)1)<<52)-1);
@@ -5944,11 +5948,11 @@
59445948
const sqlite3_api_routines *pApi
59455949
){
59465950
int rc = SQLITE_OK;
59475951
SQLITE_EXTENSION_INIT2(pApi);
59485952
#ifndef SQLITE_OMIT_VIRTUALTABLE
5949
- if( sqlite3_libversion_number()<3008012 ){
5953
+ if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){
59505954
*pzErrMsg = sqlite3_mprintf(
59515955
"generate_series() requires SQLite 3.8.12 or later");
59525956
return SQLITE_ERROR;
59535957
}
59545958
rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
@@ -7294,10 +7298,11 @@
72947298
72957299
/*
72967300
** Read and return a 32-bit little-endian unsigned integer from buffer aBuf.
72977301
*/
72987302
static u32 zipfileGetU32(const u8 *aBuf){
7303
+ if( aBuf==0 ) return 0;
72997304
return ((u32)(aBuf[3]) << 24)
73007305
+ ((u32)(aBuf[2]) << 16)
73017306
+ ((u32)(aBuf[1]) << 8)
73027307
+ ((u32)(aBuf[0]) << 0);
73037308
}
@@ -7596,11 +7601,11 @@
75967601
rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
75977602
}else{
75987603
aRead = (u8*)&aBlob[pNew->cds.iOffset];
75997604
}
76007605
7601
- rc = zipfileReadLFH(aRead, &lfh);
7606
+ if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh);
76027607
if( rc==SQLITE_OK ){
76037608
pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
76047609
pNew->iDataOff += lfh.nFile + lfh.nExtra;
76057610
if( aBlob && pNew->cds.szCompressed ){
76067611
pNew->aData = &pNew->aExtra[nExtra];
@@ -7872,17 +7877,17 @@
78727877
){
78737878
u8 *aRead = pTab->aBuffer; /* Temporary buffer */
78747879
int nRead; /* Bytes to read from file */
78757880
int rc = SQLITE_OK;
78767881
7882
+ memset(pEOCD, 0, sizeof(ZipfileEOCD));
78777883
if( aBlob==0 ){
78787884
i64 iOff; /* Offset to read from */
78797885
i64 szFile; /* Total size of file in bytes */
78807886
fseek(pFile, 0, SEEK_END);
78817887
szFile = (i64)ftell(pFile);
78827888
if( szFile==0 ){
7883
- memset(pEOCD, 0, sizeof(ZipfileEOCD));
78847889
return SQLITE_OK;
78857890
}
78867891
nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
78877892
iOff = szFile - nRead;
78887893
rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
@@ -12486,11 +12491,10 @@
1248612491
sqlite3_result_blob64(context, p, sz, sqlite3_free);
1248712492
}else{
1248812493
sqlite3_int64 i, j;
1248912494
if( hasCRNL ){
1249012495
/* If the original contains \r\n then do no conversions back to \n */
12491
- j = sz;
1249212496
}else{
1249312497
/* If the file did not originally contain \r\n then convert any new
1249412498
** \r\n back into \n */
1249512499
for(i=j=0; i<sz; i++){
1249612500
if( p[i]=='\r' && p[i+1]=='\n' ) i++;
@@ -14215,13 +14219,13 @@
1421514219
nRow++;
1421614220
for(i=0; i<nColumn; i++){
1421714221
z = (const char*)sqlite3_column_text(pStmt,i);
1421814222
azData[nRow*nColumn + i] = z ? strdup(z) : 0;
1421914223
}
14220
- }while( (rc = sqlite3_step(pStmt))==SQLITE_ROW );
14224
+ }while( sqlite3_step(pStmt)==SQLITE_ROW );
1422114225
if( nColumn>p->nWidth ){
14222
- p->colWidth = realloc(p->colWidth, nColumn*2*sizeof(int));
14226
+ p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int));
1422314227
if( p->colWidth==0 ) shell_out_of_memory();
1422414228
for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0;
1422514229
p->nWidth = nColumn;
1422614230
p->actualWidth = &p->colWidth[nColumn];
1422714231
}
@@ -14359,11 +14363,11 @@
1435914363
if( SQLITE_ROW == rc ){
1436014364
/* allocate space for col name ptr, value ptr, and type */
1436114365
int nCol = sqlite3_column_count(pStmt);
1436214366
void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1);
1436314367
if( !pData ){
14364
- rc = SQLITE_NOMEM;
14368
+ shell_out_of_memory();
1436514369
}else{
1436614370
char **azCols = (char **)pData; /* Names of result columns */
1436714371
char **azVals = &azCols[nCol]; /* Results */
1436814372
int *aiTypes = (int *)&azVals[nCol]; /* Result types */
1436914373
int i, x;
@@ -15500,11 +15504,11 @@
1550015504
rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
1550115505
&j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
1550215506
&x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
1550315507
if( rc==17 ){
1550415508
k = iOffset+j;
15505
- if( k+16<=n ){
15509
+ if( k+16<=n && k>=0 ){
1550615510
int ii;
1550715511
for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
1550815512
}
1550915513
}
1551015514
}
@@ -16417,11 +16421,11 @@
1641716421
utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
1641816422
sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
1641916423
zQuery);
1642016424
goto end_schema_xfer;
1642116425
}
16422
- while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
16426
+ while( sqlite3_step(pQuery)==SQLITE_ROW ){
1642316427
zName = sqlite3_column_text(pQuery, 0);
1642416428
zSql = sqlite3_column_text(pQuery, 1);
1642516429
printf("%s... ", zName); fflush(stdout);
1642616430
sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
1642716431
if( zErrMsg ){
@@ -16805,12 +16809,11 @@
1680516809
p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
1680616810
}else{
1680716811
p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
1680816812
}
1680916813
if( p->zTempFile==0 ){
16810
- raw_printf(stderr, "out of memory\n");
16811
- exit(1);
16814
+ shell_out_of_memory();
1681216815
}
1681316816
}
1681416817
1681516818
1681616819
/*
@@ -19621,11 +19624,11 @@
1962119624
sqlite3_finalize(pStmt);
1962219625
zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
1962319626
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
1962419627
sqlite3_free(zSql);
1962519628
i = 0;
19626
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
19629
+ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
1962719630
char zLabel[20];
1962819631
const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
1962919632
i++;
1963019633
if( zCol==0 ){
1963119634
if( sqlite3_column_int(pStmt,1)==-1 ){
@@ -19863,14 +19866,15 @@
1986319866
raw_printf(stderr, "Usage: .nonce NONCE\n");
1986419867
rc = 1;
1986519868
}else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
1986619869
raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]);
1986719870
exit(1);
19871
+ }else{
19872
+ p->bSafeMode = 0;
19873
+ return 0; /* Return immediately to bypass the safe mode reset
19874
+ ** at the end of this procedure */
1986819875
}
19869
- p->bSafeMode = 0;
19870
- return 0; /* Return immediately to bypass the safe mode reset
19871
- ** at the end of this procedure */
1987219876
}else
1987319877
1987419878
if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
1987519879
if( nArg==2 ){
1987619880
sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
@@ -20125,11 +20129,11 @@
2012520129
pStmt = 0;
2012620130
if( len ){
2012720131
rx = sqlite3_prepare_v2(p->db,
2012820132
"SELECT key, quote(value) "
2012920133
"FROM temp.sqlite_parameters;", -1, &pStmt, 0);
20130
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
20134
+ while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
2013120135
utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
2013220136
sqlite3_column_text(pStmt,1));
2013320137
}
2013420138
sqlite3_finalize(pStmt);
2013520139
}
@@ -21098,12 +21102,14 @@
2109821102
appendText(&s," WHERE type='index'"
2109921103
" AND tbl_name LIKE ?1", 0);
2110021104
}
2110121105
}
2110221106
rc = sqlite3_finalize(pStmt);
21103
- appendText(&s, " ORDER BY 1", 0);
21104
- rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
21107
+ if( rc==SQLITE_OK ){
21108
+ appendText(&s, " ORDER BY 1", 0);
21109
+ rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
21110
+ }
2110521111
freeText(&s);
2110621112
if( rc ) return shellDatabaseError(p->db);
2110721113
2110821114
/* Run the SQL statement prepared by the above block. Store the results
2110921115
** as an array of nul-terminated strings in azResult[]. */
@@ -21625,11 +21631,11 @@
2162521631
2162621632
if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
2162721633
int j;
2162821634
assert( nArg<=ArraySize(azArg) );
2162921635
p->nWidth = nArg-1;
21630
- p->colWidth = realloc(p->colWidth, p->nWidth*sizeof(int)*2);
21636
+ p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
2163121637
if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
2163221638
if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
2163321639
for(j=1; j<nArg; j++){
2163421640
p->colWidth[j-1] = (int)integerValue(azArg[j]);
2163521641
}
@@ -21674,10 +21680,11 @@
2167421680
static QuickScanState quickscan(char *zLine, QuickScanState qss){
2167521681
char cin;
2167621682
char cWait = (char)qss; /* intentional narrowing loss */
2167721683
if( cWait==0 ){
2167821684
PlainScan:
21685
+ assert( cWait==0 );
2167921686
while( (cin = *zLine++)!=0 ){
2168021687
if( IsSpace(cin) )
2168121688
continue;
2168221689
switch (cin){
2168321690
case '-':
@@ -21861,11 +21868,12 @@
2186121868
}
2186221869
qss = quickscan(zLine, qss);
2186321870
if( QSS_PLAINWHITE(qss) && nSql==0 ){
2186421871
if( ShellHasFlag(p, SHFLG_Echo) )
2186521872
printf("%s\n", zLine);
21866
- /* Just swallow leading whitespace */
21873
+ /* Just swallow single-line whitespace */
21874
+ qss = QSS_Start;
2186721875
continue;
2186821876
}
2186921877
if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
2187021878
if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
2187121879
if( zLine[0]=='.' ){
@@ -21874,12 +21882,14 @@
2187421882
break;
2187521883
}else if( rc ){
2187621884
errCnt++;
2187721885
}
2187821886
}
21887
+ qss = QSS_Start;
2187921888
continue;
2188021889
}
21890
+ /* No single-line dispositions remain; accumulate line(s). */
2188121891
nLine = strlen30(zLine);
2188221892
if( nSql+nLine+2>=nAlloc ){
2188321893
/* Grow buffer by half-again increments when big. */
2188421894
nAlloc = nSql+(nSql>>1)+nLine+100;
2188521895
zSql = realloc(zSql, nAlloc);
@@ -21905,13 +21915,15 @@
2190521915
p->outCount = 0;
2190621916
}else{
2190721917
clearTempFile(p);
2190821918
}
2190921919
p->bSafeMode = p->bSafeModePersist;
21920
+ qss = QSS_Start;
2191021921
}else if( nSql && QSS_PLAINWHITE(qss) ){
2191121922
if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
2191221923
nSql = 0;
21924
+ qss = QSS_Start;
2191321925
}
2191421926
}
2191521927
if( nSql && QSS_PLAINDARK(qss) ){
2191621928
errCnt += runOneSqlLine(p, zSql, p->in, startline);
2191721929
}
2191821930
--- src/shell.c
+++ src/shell.c
@@ -5398,11 +5398,15 @@
5398 e--;
5399 }
5400 e += 1075;
5401 if( e<=0 ){
5402 /* Subnormal */
5403 m >>= 1-e;
 
 
 
 
5404 e = 0;
5405 }else if( e>0x7ff ){
5406 e = 0x7ff;
5407 }
5408 a = m & ((((sqlite3_int64)1)<<52)-1);
@@ -5944,11 +5948,11 @@
5944 const sqlite3_api_routines *pApi
5945 ){
5946 int rc = SQLITE_OK;
5947 SQLITE_EXTENSION_INIT2(pApi);
5948 #ifndef SQLITE_OMIT_VIRTUALTABLE
5949 if( sqlite3_libversion_number()<3008012 ){
5950 *pzErrMsg = sqlite3_mprintf(
5951 "generate_series() requires SQLite 3.8.12 or later");
5952 return SQLITE_ERROR;
5953 }
5954 rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
@@ -7294,10 +7298,11 @@
7294
7295 /*
7296 ** Read and return a 32-bit little-endian unsigned integer from buffer aBuf.
7297 */
7298 static u32 zipfileGetU32(const u8 *aBuf){
 
7299 return ((u32)(aBuf[3]) << 24)
7300 + ((u32)(aBuf[2]) << 16)
7301 + ((u32)(aBuf[1]) << 8)
7302 + ((u32)(aBuf[0]) << 0);
7303 }
@@ -7596,11 +7601,11 @@
7596 rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
7597 }else{
7598 aRead = (u8*)&aBlob[pNew->cds.iOffset];
7599 }
7600
7601 rc = zipfileReadLFH(aRead, &lfh);
7602 if( rc==SQLITE_OK ){
7603 pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
7604 pNew->iDataOff += lfh.nFile + lfh.nExtra;
7605 if( aBlob && pNew->cds.szCompressed ){
7606 pNew->aData = &pNew->aExtra[nExtra];
@@ -7872,17 +7877,17 @@
7872 ){
7873 u8 *aRead = pTab->aBuffer; /* Temporary buffer */
7874 int nRead; /* Bytes to read from file */
7875 int rc = SQLITE_OK;
7876
 
7877 if( aBlob==0 ){
7878 i64 iOff; /* Offset to read from */
7879 i64 szFile; /* Total size of file in bytes */
7880 fseek(pFile, 0, SEEK_END);
7881 szFile = (i64)ftell(pFile);
7882 if( szFile==0 ){
7883 memset(pEOCD, 0, sizeof(ZipfileEOCD));
7884 return SQLITE_OK;
7885 }
7886 nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
7887 iOff = szFile - nRead;
7888 rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
@@ -12486,11 +12491,10 @@
12486 sqlite3_result_blob64(context, p, sz, sqlite3_free);
12487 }else{
12488 sqlite3_int64 i, j;
12489 if( hasCRNL ){
12490 /* If the original contains \r\n then do no conversions back to \n */
12491 j = sz;
12492 }else{
12493 /* If the file did not originally contain \r\n then convert any new
12494 ** \r\n back into \n */
12495 for(i=j=0; i<sz; i++){
12496 if( p[i]=='\r' && p[i+1]=='\n' ) i++;
@@ -14215,13 +14219,13 @@
14215 nRow++;
14216 for(i=0; i<nColumn; i++){
14217 z = (const char*)sqlite3_column_text(pStmt,i);
14218 azData[nRow*nColumn + i] = z ? strdup(z) : 0;
14219 }
14220 }while( (rc = sqlite3_step(pStmt))==SQLITE_ROW );
14221 if( nColumn>p->nWidth ){
14222 p->colWidth = realloc(p->colWidth, nColumn*2*sizeof(int));
14223 if( p->colWidth==0 ) shell_out_of_memory();
14224 for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0;
14225 p->nWidth = nColumn;
14226 p->actualWidth = &p->colWidth[nColumn];
14227 }
@@ -14359,11 +14363,11 @@
14359 if( SQLITE_ROW == rc ){
14360 /* allocate space for col name ptr, value ptr, and type */
14361 int nCol = sqlite3_column_count(pStmt);
14362 void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1);
14363 if( !pData ){
14364 rc = SQLITE_NOMEM;
14365 }else{
14366 char **azCols = (char **)pData; /* Names of result columns */
14367 char **azVals = &azCols[nCol]; /* Results */
14368 int *aiTypes = (int *)&azVals[nCol]; /* Result types */
14369 int i, x;
@@ -15500,11 +15504,11 @@
15500 rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
15501 &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
15502 &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
15503 if( rc==17 ){
15504 k = iOffset+j;
15505 if( k+16<=n ){
15506 int ii;
15507 for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
15508 }
15509 }
15510 }
@@ -16417,11 +16421,11 @@
16417 utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
16418 sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
16419 zQuery);
16420 goto end_schema_xfer;
16421 }
16422 while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
16423 zName = sqlite3_column_text(pQuery, 0);
16424 zSql = sqlite3_column_text(pQuery, 1);
16425 printf("%s... ", zName); fflush(stdout);
16426 sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
16427 if( zErrMsg ){
@@ -16805,12 +16809,11 @@
16805 p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
16806 }else{
16807 p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
16808 }
16809 if( p->zTempFile==0 ){
16810 raw_printf(stderr, "out of memory\n");
16811 exit(1);
16812 }
16813 }
16814
16815
16816 /*
@@ -19621,11 +19624,11 @@
19621 sqlite3_finalize(pStmt);
19622 zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
19623 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
19624 sqlite3_free(zSql);
19625 i = 0;
19626 while( sqlite3_step(pStmt)==SQLITE_ROW ){
19627 char zLabel[20];
19628 const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
19629 i++;
19630 if( zCol==0 ){
19631 if( sqlite3_column_int(pStmt,1)==-1 ){
@@ -19863,14 +19866,15 @@
19863 raw_printf(stderr, "Usage: .nonce NONCE\n");
19864 rc = 1;
19865 }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
19866 raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]);
19867 exit(1);
 
 
 
 
19868 }
19869 p->bSafeMode = 0;
19870 return 0; /* Return immediately to bypass the safe mode reset
19871 ** at the end of this procedure */
19872 }else
19873
19874 if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
19875 if( nArg==2 ){
19876 sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
@@ -20125,11 +20129,11 @@
20125 pStmt = 0;
20126 if( len ){
20127 rx = sqlite3_prepare_v2(p->db,
20128 "SELECT key, quote(value) "
20129 "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
20130 while( sqlite3_step(pStmt)==SQLITE_ROW ){
20131 utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
20132 sqlite3_column_text(pStmt,1));
20133 }
20134 sqlite3_finalize(pStmt);
20135 }
@@ -21098,12 +21102,14 @@
21098 appendText(&s," WHERE type='index'"
21099 " AND tbl_name LIKE ?1", 0);
21100 }
21101 }
21102 rc = sqlite3_finalize(pStmt);
21103 appendText(&s, " ORDER BY 1", 0);
21104 rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
 
 
21105 freeText(&s);
21106 if( rc ) return shellDatabaseError(p->db);
21107
21108 /* Run the SQL statement prepared by the above block. Store the results
21109 ** as an array of nul-terminated strings in azResult[]. */
@@ -21625,11 +21631,11 @@
21625
21626 if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
21627 int j;
21628 assert( nArg<=ArraySize(azArg) );
21629 p->nWidth = nArg-1;
21630 p->colWidth = realloc(p->colWidth, p->nWidth*sizeof(int)*2);
21631 if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
21632 if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
21633 for(j=1; j<nArg; j++){
21634 p->colWidth[j-1] = (int)integerValue(azArg[j]);
21635 }
@@ -21674,10 +21680,11 @@
21674 static QuickScanState quickscan(char *zLine, QuickScanState qss){
21675 char cin;
21676 char cWait = (char)qss; /* intentional narrowing loss */
21677 if( cWait==0 ){
21678 PlainScan:
 
21679 while( (cin = *zLine++)!=0 ){
21680 if( IsSpace(cin) )
21681 continue;
21682 switch (cin){
21683 case '-':
@@ -21861,11 +21868,12 @@
21861 }
21862 qss = quickscan(zLine, qss);
21863 if( QSS_PLAINWHITE(qss) && nSql==0 ){
21864 if( ShellHasFlag(p, SHFLG_Echo) )
21865 printf("%s\n", zLine);
21866 /* Just swallow leading whitespace */
 
21867 continue;
21868 }
21869 if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
21870 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
21871 if( zLine[0]=='.' ){
@@ -21874,12 +21882,14 @@
21874 break;
21875 }else if( rc ){
21876 errCnt++;
21877 }
21878 }
 
21879 continue;
21880 }
 
21881 nLine = strlen30(zLine);
21882 if( nSql+nLine+2>=nAlloc ){
21883 /* Grow buffer by half-again increments when big. */
21884 nAlloc = nSql+(nSql>>1)+nLine+100;
21885 zSql = realloc(zSql, nAlloc);
@@ -21905,13 +21915,15 @@
21905 p->outCount = 0;
21906 }else{
21907 clearTempFile(p);
21908 }
21909 p->bSafeMode = p->bSafeModePersist;
 
21910 }else if( nSql && QSS_PLAINWHITE(qss) ){
21911 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
21912 nSql = 0;
 
21913 }
21914 }
21915 if( nSql && QSS_PLAINDARK(qss) ){
21916 errCnt += runOneSqlLine(p, zSql, p->in, startline);
21917 }
21918
--- src/shell.c
+++ src/shell.c
@@ -5398,11 +5398,15 @@
5398 e--;
5399 }
5400 e += 1075;
5401 if( e<=0 ){
5402 /* Subnormal */
5403 if( 1-e >= 64 ){
5404 m = 0;
5405 }else{
5406 m >>= 1-e;
5407 }
5408 e = 0;
5409 }else if( e>0x7ff ){
5410 e = 0x7ff;
5411 }
5412 a = m & ((((sqlite3_int64)1)<<52)-1);
@@ -5944,11 +5948,11 @@
5948 const sqlite3_api_routines *pApi
5949 ){
5950 int rc = SQLITE_OK;
5951 SQLITE_EXTENSION_INIT2(pApi);
5952 #ifndef SQLITE_OMIT_VIRTUALTABLE
5953 if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){
5954 *pzErrMsg = sqlite3_mprintf(
5955 "generate_series() requires SQLite 3.8.12 or later");
5956 return SQLITE_ERROR;
5957 }
5958 rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
@@ -7294,10 +7298,11 @@
7298
7299 /*
7300 ** Read and return a 32-bit little-endian unsigned integer from buffer aBuf.
7301 */
7302 static u32 zipfileGetU32(const u8 *aBuf){
7303 if( aBuf==0 ) return 0;
7304 return ((u32)(aBuf[3]) << 24)
7305 + ((u32)(aBuf[2]) << 16)
7306 + ((u32)(aBuf[1]) << 8)
7307 + ((u32)(aBuf[0]) << 0);
7308 }
@@ -7596,11 +7601,11 @@
7601 rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
7602 }else{
7603 aRead = (u8*)&aBlob[pNew->cds.iOffset];
7604 }
7605
7606 if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh);
7607 if( rc==SQLITE_OK ){
7608 pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
7609 pNew->iDataOff += lfh.nFile + lfh.nExtra;
7610 if( aBlob && pNew->cds.szCompressed ){
7611 pNew->aData = &pNew->aExtra[nExtra];
@@ -7872,17 +7877,17 @@
7877 ){
7878 u8 *aRead = pTab->aBuffer; /* Temporary buffer */
7879 int nRead; /* Bytes to read from file */
7880 int rc = SQLITE_OK;
7881
7882 memset(pEOCD, 0, sizeof(ZipfileEOCD));
7883 if( aBlob==0 ){
7884 i64 iOff; /* Offset to read from */
7885 i64 szFile; /* Total size of file in bytes */
7886 fseek(pFile, 0, SEEK_END);
7887 szFile = (i64)ftell(pFile);
7888 if( szFile==0 ){
 
7889 return SQLITE_OK;
7890 }
7891 nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
7892 iOff = szFile - nRead;
7893 rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
@@ -12486,11 +12491,10 @@
12491 sqlite3_result_blob64(context, p, sz, sqlite3_free);
12492 }else{
12493 sqlite3_int64 i, j;
12494 if( hasCRNL ){
12495 /* If the original contains \r\n then do no conversions back to \n */
 
12496 }else{
12497 /* If the file did not originally contain \r\n then convert any new
12498 ** \r\n back into \n */
12499 for(i=j=0; i<sz; i++){
12500 if( p[i]=='\r' && p[i+1]=='\n' ) i++;
@@ -14215,13 +14219,13 @@
14219 nRow++;
14220 for(i=0; i<nColumn; i++){
14221 z = (const char*)sqlite3_column_text(pStmt,i);
14222 azData[nRow*nColumn + i] = z ? strdup(z) : 0;
14223 }
14224 }while( sqlite3_step(pStmt)==SQLITE_ROW );
14225 if( nColumn>p->nWidth ){
14226 p->colWidth = realloc(p->colWidth, (nColumn+1)*2*sizeof(int));
14227 if( p->colWidth==0 ) shell_out_of_memory();
14228 for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0;
14229 p->nWidth = nColumn;
14230 p->actualWidth = &p->colWidth[nColumn];
14231 }
@@ -14359,11 +14363,11 @@
14363 if( SQLITE_ROW == rc ){
14364 /* allocate space for col name ptr, value ptr, and type */
14365 int nCol = sqlite3_column_count(pStmt);
14366 void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1);
14367 if( !pData ){
14368 shell_out_of_memory();
14369 }else{
14370 char **azCols = (char **)pData; /* Names of result columns */
14371 char **azVals = &azCols[nCol]; /* Results */
14372 int *aiTypes = (int *)&azVals[nCol]; /* Result types */
14373 int i, x;
@@ -15500,11 +15504,11 @@
15504 rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
15505 &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
15506 &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
15507 if( rc==17 ){
15508 k = iOffset+j;
15509 if( k+16<=n && k>=0 ){
15510 int ii;
15511 for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
15512 }
15513 }
15514 }
@@ -16417,11 +16421,11 @@
16421 utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
16422 sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
16423 zQuery);
16424 goto end_schema_xfer;
16425 }
16426 while( sqlite3_step(pQuery)==SQLITE_ROW ){
16427 zName = sqlite3_column_text(pQuery, 0);
16428 zSql = sqlite3_column_text(pQuery, 1);
16429 printf("%s... ", zName); fflush(stdout);
16430 sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
16431 if( zErrMsg ){
@@ -16805,12 +16809,11 @@
16809 p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
16810 }else{
16811 p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
16812 }
16813 if( p->zTempFile==0 ){
16814 shell_out_of_memory();
 
16815 }
16816 }
16817
16818
16819 /*
@@ -19621,11 +19624,11 @@
19624 sqlite3_finalize(pStmt);
19625 zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
19626 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
19627 sqlite3_free(zSql);
19628 i = 0;
19629 while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
19630 char zLabel[20];
19631 const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
19632 i++;
19633 if( zCol==0 ){
19634 if( sqlite3_column_int(pStmt,1)==-1 ){
@@ -19863,14 +19866,15 @@
19866 raw_printf(stderr, "Usage: .nonce NONCE\n");
19867 rc = 1;
19868 }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
19869 raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", p->lineno, azArg[1]);
19870 exit(1);
19871 }else{
19872 p->bSafeMode = 0;
19873 return 0; /* Return immediately to bypass the safe mode reset
19874 ** at the end of this procedure */
19875 }
 
 
 
19876 }else
19877
19878 if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
19879 if( nArg==2 ){
19880 sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
@@ -20125,11 +20129,11 @@
20129 pStmt = 0;
20130 if( len ){
20131 rx = sqlite3_prepare_v2(p->db,
20132 "SELECT key, quote(value) "
20133 "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
20134 while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
20135 utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
20136 sqlite3_column_text(pStmt,1));
20137 }
20138 sqlite3_finalize(pStmt);
20139 }
@@ -21098,12 +21102,14 @@
21102 appendText(&s," WHERE type='index'"
21103 " AND tbl_name LIKE ?1", 0);
21104 }
21105 }
21106 rc = sqlite3_finalize(pStmt);
21107 if( rc==SQLITE_OK ){
21108 appendText(&s, " ORDER BY 1", 0);
21109 rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
21110 }
21111 freeText(&s);
21112 if( rc ) return shellDatabaseError(p->db);
21113
21114 /* Run the SQL statement prepared by the above block. Store the results
21115 ** as an array of nul-terminated strings in azResult[]. */
@@ -21625,11 +21631,11 @@
21631
21632 if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
21633 int j;
21634 assert( nArg<=ArraySize(azArg) );
21635 p->nWidth = nArg-1;
21636 p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
21637 if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
21638 if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
21639 for(j=1; j<nArg; j++){
21640 p->colWidth[j-1] = (int)integerValue(azArg[j]);
21641 }
@@ -21674,10 +21680,11 @@
21680 static QuickScanState quickscan(char *zLine, QuickScanState qss){
21681 char cin;
21682 char cWait = (char)qss; /* intentional narrowing loss */
21683 if( cWait==0 ){
21684 PlainScan:
21685 assert( cWait==0 );
21686 while( (cin = *zLine++)!=0 ){
21687 if( IsSpace(cin) )
21688 continue;
21689 switch (cin){
21690 case '-':
@@ -21861,11 +21868,12 @@
21868 }
21869 qss = quickscan(zLine, qss);
21870 if( QSS_PLAINWHITE(qss) && nSql==0 ){
21871 if( ShellHasFlag(p, SHFLG_Echo) )
21872 printf("%s\n", zLine);
21873 /* Just swallow single-line whitespace */
21874 qss = QSS_Start;
21875 continue;
21876 }
21877 if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
21878 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
21879 if( zLine[0]=='.' ){
@@ -21874,12 +21882,14 @@
21882 break;
21883 }else if( rc ){
21884 errCnt++;
21885 }
21886 }
21887 qss = QSS_Start;
21888 continue;
21889 }
21890 /* No single-line dispositions remain; accumulate line(s). */
21891 nLine = strlen30(zLine);
21892 if( nSql+nLine+2>=nAlloc ){
21893 /* Grow buffer by half-again increments when big. */
21894 nAlloc = nSql+(nSql>>1)+nLine+100;
21895 zSql = realloc(zSql, nAlloc);
@@ -21905,13 +21915,15 @@
21915 p->outCount = 0;
21916 }else{
21917 clearTempFile(p);
21918 }
21919 p->bSafeMode = p->bSafeModePersist;
21920 qss = QSS_Start;
21921 }else if( nSql && QSS_PLAINWHITE(qss) ){
21922 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
21923 nSql = 0;
21924 qss = QSS_Start;
21925 }
21926 }
21927 if( nSql && QSS_PLAINDARK(qss) ){
21928 errCnt += runOneSqlLine(p, zSql, p->in, startline);
21929 }
21930
+506 -351
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -452,11 +452,11 @@
452452
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
453453
** [sqlite_version()] and [sqlite_source_id()].
454454
*/
455455
#define SQLITE_VERSION "3.37.0"
456456
#define SQLITE_VERSION_NUMBER 3037000
457
-#define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
457
+#define SQLITE_SOURCE_ID "2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813"
458458
459459
/*
460460
** CAPI3REF: Run-Time Library Version Numbers
461461
** KEYWORDS: sqlite3_version sqlite3_sourceid
462462
**
@@ -13303,10 +13303,17 @@
1330313303
*/
1330413304
#ifdef SQLITE_OMIT_EXPLAIN
1330513305
# undef SQLITE_ENABLE_EXPLAIN_COMMENTS
1330613306
#endif
1330713307
13308
+/*
13309
+** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE
13310
+*/
13311
+#if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE)
13312
+# define SQLITE_OMIT_ALTERTABLE
13313
+#endif
13314
+
1330813315
/*
1330913316
** Return true (non-zero) if the input is an integer that is too large
1331013317
** to fit in 32-bits. This macro is used inside of various testcase()
1331113318
** macros to verify that we have tested SQLite for large-file support.
1331213319
*/
@@ -16403,11 +16410,11 @@
1640316410
u8 iDb; /* Which db file is being initialized */
1640416411
u8 busy; /* TRUE if currently initializing */
1640516412
unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
1640616413
unsigned imposterTable : 1; /* Building an imposter table */
1640716414
unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
16408
- char **azInit; /* "type", "name", and "tbl_name" columns */
16415
+ const char **azInit; /* "type", "name", and "tbl_name" columns */
1640916416
} init;
1641016417
int nVdbeActive; /* Number of VDBEs currently running */
1641116418
int nVdbeRead; /* Number of active VDBEs that read or write */
1641216419
int nVdbeWrite; /* Number of active VDBEs that read and write */
1641316420
int nVdbeExec; /* Number of nested calls to VdbeExec() */
@@ -18967,11 +18974,11 @@
1896718974
SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window*);
1896818975
SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p);
1896918976
SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8);
1897018977
SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*);
1897118978
SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin);
18972
-SQLITE_PRIVATE int sqlite3WindowCompare(Parse*, Window*, Window*, int);
18979
+SQLITE_PRIVATE int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int);
1897318980
SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*);
1897418981
SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int);
1897518982
SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*);
1897618983
SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*);
1897718984
SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p);
@@ -19099,12 +19106,12 @@
1909919106
SQLITE_PRIVATE void *sqlite3Realloc(void*, u64);
1910019107
SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
1910119108
SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64);
1910219109
SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*);
1910319110
SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*);
19104
-SQLITE_PRIVATE int sqlite3MallocSize(void*);
19105
-SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*);
19111
+SQLITE_PRIVATE int sqlite3MallocSize(const void*);
19112
+SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*);
1910619113
SQLITE_PRIVATE void *sqlite3PageMalloc(int);
1910719114
SQLITE_PRIVATE void sqlite3PageFree(void*);
1910819115
SQLITE_PRIVATE void sqlite3MemSetDefault(void);
1910919116
#ifndef SQLITE_UNTESTABLE
1911019117
SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void));
@@ -19236,21 +19243,21 @@
1923619243
SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*);
1923719244
SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*);
1923819245
SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*);
1923919246
SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*);
1924019247
SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*);
19241
-SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int);
19242
-SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*);
19248
+SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int);
19249
+SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*);
1924319250
SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
1924419251
SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*);
1924519252
SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*);
1924619253
SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*);
1924719254
SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
1924819255
SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
1924919256
SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*);
1925019257
SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int);
19251
-SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
19258
+SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int);
1925219259
SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*);
1925319260
SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*);
1925419261
SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*);
1925519262
SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index*);
1925619263
SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**);
@@ -19429,15 +19436,15 @@
1942919436
SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
1943019437
SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
1943119438
SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
1943219439
SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*);
1943319440
SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*);
19434
-SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*);
19435
-SQLITE_PRIVATE int sqlite3ExprCompare(Parse*,Expr*, Expr*, int);
19436
-SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int);
19437
-SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int);
19438
-SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int);
19441
+SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, const Token*);
19442
+SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int);
19443
+SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int);
19444
+SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int);
19445
+SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int);
1943919446
SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int);
1944019447
SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*);
1944119448
SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
1944219449
SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
1944319450
SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
@@ -19464,11 +19471,11 @@
1946419471
SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
1946519472
SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
1946619473
#ifdef SQLITE_ENABLE_CURSOR_HINTS
1946719474
SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
1946819475
#endif
19469
-SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*);
19476
+SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*);
1947019477
SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
1947119478
SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
1947219479
SQLITE_PRIVATE int sqlite3IsRowid(const char*);
1947319480
SQLITE_PRIVATE void sqlite3GenerateRowDelete(
1947419481
Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
@@ -19489,15 +19496,15 @@
1948919496
SQLITE_PRIVATE void sqlite3MultiWrite(Parse*);
1949019497
SQLITE_PRIVATE void sqlite3MayAbort(Parse*);
1949119498
SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8);
1949219499
SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*);
1949319500
SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*);
19494
-SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int);
19495
-SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int);
19496
-SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int);
19497
-SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*);
19498
-SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int);
19501
+SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,const Expr*,int);
19502
+SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int);
19503
+SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int);
19504
+SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,const IdList*);
19505
+SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,const Select*,int);
1949919506
SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*);
1950019507
SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int);
1950119508
SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8);
1950219509
SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void);
1950319510
SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void);
@@ -19631,11 +19638,11 @@
1963119638
1963219639
SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
1963319640
SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
1963419641
SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
1963519642
SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
19636
-SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
19643
+SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table*,int);
1963719644
SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr);
1963819645
SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
1963919646
SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
1964019647
SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
1964119648
SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
@@ -19660,12 +19667,12 @@
1966019667
SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
1966119668
SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8);
1966219669
SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr);
1966319670
SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr);
1966419671
SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*);
19665
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
19666
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
19672
+SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int);
19673
+SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*);
1966719674
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
1966819675
SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
1966919676
SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
1967019677
SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*);
1967119678
SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*);
@@ -19692,11 +19699,11 @@
1969219699
#endif
1969319700
SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *);
1969419701
#ifndef SQLITE_OMIT_UTF16
1969519702
SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8);
1969619703
#endif
19697
-SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **);
19704
+SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **);
1969819705
SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
1969919706
#ifndef SQLITE_AMALGAMATION
1970019707
SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
1970119708
SQLITE_PRIVATE const char sqlite3StrBINARY[];
1970219709
SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[];
@@ -19744,13 +19751,13 @@
1974419751
SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
1974519752
SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
1974619753
SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
1974719754
SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
1974819755
SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
19749
-SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, Token*);
19750
-SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse*, void*, Token*);
19751
-SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom);
19756
+SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*);
19757
+SQLITE_PRIVATE const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*);
19758
+SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom);
1975219759
SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*);
1975319760
SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*);
1975419761
SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
1975519762
SQLITE_PRIVATE char sqlite3AffinityType(const char*, Column*);
1975619763
SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*);
@@ -19790,10 +19797,12 @@
1979019797
SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
1979119798
SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
1979219799
1979319800
SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
1979419801
SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
19802
+SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum*, u8);
19803
+SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*);
1979519804
SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
1979619805
SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
1979719806
1979819807
SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *);
1979919808
SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
@@ -20021,11 +20030,11 @@
2002120030
SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p);
2002220031
SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *);
2002320032
2002420033
SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p);
2002520034
#if SQLITE_MAX_EXPR_DEPTH>0
20026
-SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *);
20035
+SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *);
2002720036
SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int);
2002820037
#else
2002920038
#define sqlite3SelectExprHeight(x) 0
2003020039
#define sqlite3ExprCheckHeight(x,y)
2003120040
#endif
@@ -20118,12 +20127,12 @@
2011820127
#endif
2011920128
#if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)
2012020129
SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*);
2012120130
#endif
2012220131
20123
-SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr);
20124
-SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr);
20132
+SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr);
20133
+SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr);
2012520134
SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int);
2012620135
SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int);
2012720136
SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*);
2012820137
2012920138
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
@@ -23548,135 +23557,104 @@
2354823557
sqlite3_context *context,
2354923558
int argc,
2355023559
sqlite3_value **argv
2355123560
){
2355223561
DateTime x;
23553
- u64 n;
2355423562
size_t i,j;
23555
- char *z;
2355623563
sqlite3 *db;
2355723564
const char *zFmt;
23558
- char zBuf[100];
23565
+ sqlite3_str sRes;
23566
+
23567
+
2355923568
if( argc==0 ) return;
2356023569
zFmt = (const char*)sqlite3_value_text(argv[0]);
2356123570
if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
2356223571
db = sqlite3_context_db_handle(context);
23563
- for(i=0, n=1; zFmt[i]; i++, n++){
23564
- if( zFmt[i]=='%' ){
23565
- switch( zFmt[i+1] ){
23566
- case 'd':
23567
- case 'H':
23568
- case 'm':
23569
- case 'M':
23570
- case 'S':
23571
- case 'W':
23572
- n++;
23573
- /* fall thru */
23574
- case 'w':
23575
- case '%':
23576
- break;
23577
- case 'f':
23578
- n += 8;
23579
- break;
23580
- case 'j':
23581
- n += 3;
23582
- break;
23583
- case 'Y':
23584
- n += 8;
23585
- break;
23586
- case 's':
23587
- case 'J':
23588
- n += 50;
23589
- break;
23590
- default:
23591
- return; /* ERROR. return a NULL */
23592
- }
23593
- i++;
23594
- }
23595
- }
23596
- testcase( n==sizeof(zBuf)-1 );
23597
- testcase( n==sizeof(zBuf) );
23598
- testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 );
23599
- testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH] );
23600
- if( n<sizeof(zBuf) ){
23601
- z = zBuf;
23602
- }else if( n>(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){
23603
- sqlite3_result_error_toobig(context);
23604
- return;
23605
- }else{
23606
- z = sqlite3DbMallocRawNN(db, (int)n);
23607
- if( z==0 ){
23608
- sqlite3_result_error_nomem(context);
23609
- return;
23610
- }
23611
- }
23572
+ sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
23573
+
2361223574
computeJD(&x);
2361323575
computeYMD_HMS(&x);
2361423576
for(i=j=0; zFmt[i]; i++){
23615
- if( zFmt[i]!='%' ){
23616
- z[j++] = zFmt[i];
23617
- }else{
23618
- i++;
23619
- switch( zFmt[i] ){
23620
- case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break;
23621
- case 'f': {
23622
- double s = x.s;
23623
- if( s>59.999 ) s = 59.999;
23624
- sqlite3_snprintf(7, &z[j],"%06.3f", s);
23625
- j += sqlite3Strlen30(&z[j]);
23626
- break;
23627
- }
23628
- case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break;
23629
- case 'W': /* Fall thru */
23630
- case 'j': {
23631
- int nDay; /* Number of days since 1st day of year */
23632
- DateTime y = x;
23633
- y.validJD = 0;
23634
- y.M = 1;
23635
- y.D = 1;
23636
- computeJD(&y);
23637
- nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
23638
- if( zFmt[i]=='W' ){
23639
- int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
23640
- wd = (int)(((x.iJD+43200000)/86400000)%7);
23641
- sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
23642
- j += 2;
23643
- }else{
23644
- sqlite3_snprintf(4, &z[j],"%03d",nDay+1);
23645
- j += 3;
23646
- }
23647
- break;
23648
- }
23649
- case 'J': {
23650
- sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0);
23651
- j+=sqlite3Strlen30(&z[j]);
23652
- break;
23653
- }
23654
- case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break;
23655
- case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
23656
- case 's': {
23657
- i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000);
23658
- sqlite3Int64ToText(iS, &z[j]);
23659
- j += sqlite3Strlen30(&z[j]);
23660
- break;
23661
- }
23662
- case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
23663
- case 'w': {
23664
- z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0';
23665
- break;
23666
- }
23667
- case 'Y': {
23668
- sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]);
23669
- break;
23670
- }
23671
- default: z[j++] = '%'; break;
23672
- }
23673
- }
23674
- }
23675
- z[j] = 0;
23676
- sqlite3_result_text(context, z, -1,
23677
- z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC);
23577
+ if( zFmt[i]!='%' ) continue;
23578
+ if( j<i ) sqlite3_str_append(&sRes, zFmt+j, i-j);
23579
+ i++;
23580
+ j = i + 1;
23581
+ switch( zFmt[i] ){
23582
+ case 'd': {
23583
+ sqlite3_str_appendf(&sRes, "%02d", x.D);
23584
+ break;
23585
+ }
23586
+ case 'f': {
23587
+ double s = x.s;
23588
+ if( s>59.999 ) s = 59.999;
23589
+ sqlite3_str_appendf(&sRes, "%06.3f", s);
23590
+ break;
23591
+ }
23592
+ case 'H': {
23593
+ sqlite3_str_appendf(&sRes, "%02d", x.h);
23594
+ break;
23595
+ }
23596
+ case 'W': /* Fall thru */
23597
+ case 'j': {
23598
+ int nDay; /* Number of days since 1st day of year */
23599
+ DateTime y = x;
23600
+ y.validJD = 0;
23601
+ y.M = 1;
23602
+ y.D = 1;
23603
+ computeJD(&y);
23604
+ nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
23605
+ if( zFmt[i]=='W' ){
23606
+ int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
23607
+ wd = (int)(((x.iJD+43200000)/86400000)%7);
23608
+ sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7);
23609
+ }else{
23610
+ sqlite3_str_appendf(&sRes,"%03d",nDay+1);
23611
+ }
23612
+ break;
23613
+ }
23614
+ case 'J': {
23615
+ sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0);
23616
+ break;
23617
+ }
23618
+ case 'm': {
23619
+ sqlite3_str_appendf(&sRes,"%02d",x.M);
23620
+ break;
23621
+ }
23622
+ case 'M': {
23623
+ sqlite3_str_appendf(&sRes,"%02d",x.m);
23624
+ break;
23625
+ }
23626
+ case 's': {
23627
+ i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000);
23628
+ sqlite3_str_appendf(&sRes,"%lld",iS);
23629
+ break;
23630
+ }
23631
+ case 'S': {
23632
+ sqlite3_str_appendf(&sRes,"%02d",(int)x.s);
23633
+ break;
23634
+ }
23635
+ case 'w': {
23636
+ sqlite3_str_appendchar(&sRes, 1,
23637
+ (char)(((x.iJD+129600000)/86400000) % 7) + '0');
23638
+ break;
23639
+ }
23640
+ case 'Y': {
23641
+ sqlite3_str_appendf(&sRes,"%04d",x.Y);
23642
+ break;
23643
+ }
23644
+ case '%': {
23645
+ sqlite3_str_appendchar(&sRes, 1, '%');
23646
+ break;
23647
+ }
23648
+ default: {
23649
+ sqlite3_str_reset(&sRes);
23650
+ return;
23651
+ }
23652
+ }
23653
+ }
23654
+ if( j<i ) sqlite3_str_append(&sRes, zFmt+j, i-j);
23655
+ sqlite3ResultStrAccum(context, &sRes);
2367823656
}
2367923657
2368023658
/*
2368123659
** current_time()
2368223660
**
@@ -23953,10 +23931,11 @@
2395323931
SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){
2395423932
int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize;
2395523933
return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
2395623934
}
2395723935
SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){
23936
+ if( NEVER(id->pMethods==0) ) return 0;
2395823937
return id->pMethods->xDeviceCharacteristics(id);
2395923938
}
2396023939
#ifndef SQLITE_OMIT_WAL
2396123940
SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int offset, int n, int flags){
2396223941
return id->pMethods->xShmLock(id, offset, n, flags);
@@ -28270,11 +28249,11 @@
2827028249
2827128250
/*
2827228251
** TRUE if p is a lookaside memory allocation from db
2827328252
*/
2827428253
#ifndef SQLITE_OMIT_LOOKASIDE
28275
-static int isLookaside(sqlite3 *db, void *p){
28254
+static int isLookaside(sqlite3 *db, const void *p){
2827628255
return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd);
2827728256
}
2827828257
#else
2827928258
#define isLookaside(A,B) 0
2828028259
#endif
@@ -28281,22 +28260,22 @@
2828128260
2828228261
/*
2828328262
** Return the size of a memory allocation previously obtained from
2828428263
** sqlite3Malloc() or sqlite3_malloc().
2828528264
*/
28286
-SQLITE_PRIVATE int sqlite3MallocSize(void *p){
28265
+SQLITE_PRIVATE int sqlite3MallocSize(const void *p){
2828728266
assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
28288
- return sqlite3GlobalConfig.m.xSize(p);
28267
+ return sqlite3GlobalConfig.m.xSize((void*)p);
2828928268
}
28290
-static int lookasideMallocSize(sqlite3 *db, void *p){
28269
+static int lookasideMallocSize(sqlite3 *db, const void *p){
2829128270
#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
2829228271
return p<db->lookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL;
2829328272
#else
2829428273
return db->lookaside.szTrue;
2829528274
#endif
2829628275
}
28297
-SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){
28276
+SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){
2829828277
assert( p!=0 );
2829928278
#ifdef SQLITE_DEBUG
2830028279
if( db==0 || !isLookaside(db,p) ){
2830128280
if( db==0 ){
2830228281
assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
@@ -28319,11 +28298,11 @@
2831928298
assert( sqlite3_mutex_held(db->mutex) );
2832028299
return db->lookaside.szTrue;
2832128300
}
2832228301
}
2832328302
}
28324
- return sqlite3GlobalConfig.m.xSize(p);
28303
+ return sqlite3GlobalConfig.m.xSize((void*)p);
2832528304
}
2832628305
SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){
2832728306
assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
2832828307
assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
2832928308
return p ? sqlite3GlobalConfig.m.xSize(p) : 0;
@@ -28929,11 +28908,11 @@
2892928908
#endif /* SQLITE_OMIT_FLOATING_POINT */
2893028909
2893128910
/*
2893228911
** Set the StrAccum object to an error mode.
2893328912
*/
28934
-static void setStrAccumError(StrAccum *p, u8 eError){
28913
+SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum *p, u8 eError){
2893528914
assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
2893628915
p->accError = eError;
2893728916
if( p->mxAlloc ) sqlite3_str_reset(p);
2893828917
if( eError==SQLITE_TOOBIG ) sqlite3ErrorToParser(p->db, eError);
2893928918
}
@@ -28965,16 +28944,16 @@
2896528944
*/
2896628945
static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){
2896728946
char *z;
2896828947
if( pAccum->accError ) return 0;
2896928948
if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
28970
- setStrAccumError(pAccum, SQLITE_TOOBIG);
28949
+ sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG);
2897128950
return 0;
2897228951
}
2897328952
z = sqlite3DbMallocRaw(pAccum->db, n);
2897428953
if( z==0 ){
28975
- setStrAccumError(pAccum, SQLITE_NOMEM);
28954
+ sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM);
2897628955
}
2897728956
return z;
2897828957
}
2897928958
2898028959
/*
@@ -29709,11 +29688,11 @@
2970929688
testcase(p->accError==SQLITE_TOOBIG);
2971029689
testcase(p->accError==SQLITE_NOMEM);
2971129690
return 0;
2971229691
}
2971329692
if( p->mxAlloc==0 ){
29714
- setStrAccumError(p, SQLITE_TOOBIG);
29693
+ sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
2971529694
return p->nAlloc - p->nChar - 1;
2971629695
}else{
2971729696
char *zOld = isMalloced(p) ? p->zText : 0;
2971829697
i64 szNew = p->nChar;
2971929698
szNew += (sqlite3_int64)N + 1;
@@ -29722,11 +29701,11 @@
2972229701
** to avoid having to call this routine too often */
2972329702
szNew += p->nChar;
2972429703
}
2972529704
if( szNew > p->mxAlloc ){
2972629705
sqlite3_str_reset(p);
29727
- setStrAccumError(p, SQLITE_TOOBIG);
29706
+ sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
2972829707
return 0;
2972929708
}else{
2973029709
p->nAlloc = (int)szNew;
2973129710
}
2973229711
if( p->db ){
@@ -29740,11 +29719,11 @@
2974029719
p->zText = zNew;
2974129720
p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
2974229721
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
2974329722
}else{
2974429723
sqlite3_str_reset(p);
29745
- setStrAccumError(p, SQLITE_NOMEM);
29724
+ sqlite3StrAccumSetError(p, SQLITE_NOMEM);
2974629725
return 0;
2974729726
}
2974829727
}
2974929728
return N;
2975029729
}
@@ -29813,11 +29792,11 @@
2981329792
zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
2981429793
if( zText ){
2981529794
memcpy(zText, p->zText, p->nChar+1);
2981629795
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
2981729796
}else{
29818
- setStrAccumError(p, SQLITE_NOMEM);
29797
+ sqlite3StrAccumSetError(p, SQLITE_NOMEM);
2981929798
}
2982029799
p->zText = zText;
2982129800
return zText;
2982229801
}
2982329802
SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
@@ -29827,10 +29806,26 @@
2982729806
return strAccumFinishRealloc(p);
2982829807
}
2982929808
}
2983029809
return p->zText;
2983129810
}
29811
+
29812
+/*
29813
+** Use the content of the StrAccum passed as the second argument
29814
+** as the result of an SQL function.
29815
+*/
29816
+SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){
29817
+ if( p->accError ){
29818
+ sqlite3_result_error_code(pCtx, p->accError);
29819
+ sqlite3_str_reset(p);
29820
+ }else if( isMalloced(p) ){
29821
+ sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC);
29822
+ }else{
29823
+ sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
29824
+ sqlite3_str_reset(p);
29825
+ }
29826
+}
2983229827
2983329828
/*
2983429829
** This singleton is an sqlite3_str object that is returned if
2983529830
** sqlite3_malloc() fails to provide space for a real one. This
2983629831
** sqlite3_str object accepts no new text and always returns
@@ -57339,10 +57334,11 @@
5733957334
#endif
5734057335
}else{
5734157336
pPager->zWal = 0;
5734257337
}
5734357338
#endif
57339
+ (void)pPtr; /* Suppress warning about unused pPtr value */
5734457340
5734557341
if( nPathname ) sqlite3DbFree(0, zPathname);
5734657342
pPager->pVfs = pVfs;
5734757343
pPager->vfsFlags = vfsFlags;
5734857344
@@ -70866,13 +70862,11 @@
7086670862
if( rc==SQLITE_OK ){
7086770863
getCellInfo(pCur);
7086870864
if( pCur->info.nKey==intKey ){
7086970865
return SQLITE_OK;
7087070866
}
70871
- }else if( rc==SQLITE_DONE ){
70872
- rc = SQLITE_OK;
70873
- }else{
70867
+ }else if( rc!=SQLITE_DONE ){
7087470868
return rc;
7087570869
}
7087670870
}
7087770871
}
7087870872
}
@@ -74541,11 +74535,11 @@
7454174535
nIn = pSrc->pBt->usableSize - 4;
7454274536
}
7454374537
}
7454474538
}while( rc==SQLITE_OK && nOut>0 );
7454574539
74546
- if( rc==SQLITE_OK && nRem>0 ){
74540
+ if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){
7454774541
Pgno pgnoNew;
7454874542
MemPage *pNew = 0;
7454974543
rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0);
7455074544
put4byte(pPgnoOut, pgnoNew);
7455174545
if( ISAUTOVACUUM && pPageOut ){
@@ -78403,11 +78397,11 @@
7840378397
** NULL and an SQLite error code returned.
7840478398
*/
7840578399
#ifdef SQLITE_ENABLE_STAT4
7840678400
static int valueFromFunction(
7840778401
sqlite3 *db, /* The database connection */
78408
- Expr *p, /* The expression to evaluate */
78402
+ const Expr *p, /* The expression to evaluate */
7840978403
u8 enc, /* Encoding to use */
7841078404
u8 aff, /* Affinity to use */
7841178405
sqlite3_value **ppVal, /* Write the new value here */
7841278406
struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
7841378407
){
@@ -78497,11 +78491,11 @@
7849778491
** NULL, it is assumed that the caller will free any allocated object
7849878492
** in all cases.
7849978493
*/
7850078494
static int valueFromExpr(
7850178495
sqlite3 *db, /* The database connection */
78502
- Expr *pExpr, /* The expression to evaluate */
78496
+ const Expr *pExpr, /* The expression to evaluate */
7850378497
u8 enc, /* Encoding to use */
7850478498
u8 affinity, /* Affinity to use */
7850578499
sqlite3_value **ppVal, /* Write the new value here */
7850678500
struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
7850778501
){
@@ -78652,11 +78646,11 @@
7865278646
** the value by passing it to sqlite3ValueFree() later on. If the expression
7865378647
** cannot be converted to a value, then *ppVal is set to NULL.
7865478648
*/
7865578649
SQLITE_PRIVATE int sqlite3ValueFromExpr(
7865678650
sqlite3 *db, /* The database connection */
78657
- Expr *pExpr, /* The expression to evaluate */
78651
+ const Expr *pExpr, /* The expression to evaluate */
7865878652
u8 enc, /* Encoding to use */
7865978653
u8 affinity, /* Affinity to use */
7866078654
sqlite3_value **ppVal /* Write the new value here */
7866178655
){
7866278656
return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
@@ -86316,15 +86310,13 @@
8631686310
Mem *pVar; /* Value of a host parameter */
8631786311
StrAccum out; /* Accumulate the output here */
8631886312
#ifndef SQLITE_OMIT_UTF16
8631986313
Mem utf8; /* Used to convert UTF16 into UTF8 for display */
8632086314
#endif
86321
- char zBase[100]; /* Initial working space */
8632286315
8632386316
db = p->db;
86324
- sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase),
86325
- db->aLimit[SQLITE_LIMIT_LENGTH]);
86317
+ sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
8632686318
if( db->nVdbeExec>1 ){
8632786319
while( *zRawSql ){
8632886320
const char *zStart = zRawSql;
8632986321
while( *(zRawSql++)!='\n' && *zRawSql );
8633086322
sqlite3_str_append(&out, "-- ", 3);
@@ -94164,11 +94156,10 @@
9416494156
assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int );
9416594157
nArg = (int)pArgc->u.i;
9416694158
iQuery = (int)pQuery->u.i;
9416794159
9416894160
/* Invoke the xFilter method */
94169
- res = 0;
9417094161
apArg = p->apArg;
9417194162
for(i = 0; i<nArg; i++){
9417294163
apArg[i] = &pArgc[i+1];
9417394164
}
9417494165
rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg);
@@ -94254,11 +94245,10 @@
9425494245
sqlite3_vtab *pVtab;
9425594246
const sqlite3_module *pModule;
9425694247
int res;
9425794248
VdbeCursor *pCur;
9425894249
94259
- res = 0;
9426094250
pCur = p->apCsr[pOp->p1];
9426194251
assert( pCur->eCurType==CURTYPE_VTAB );
9426294252
if( pCur->nullRow ){
9426394253
break;
9426494254
}
@@ -100392,13 +100382,15 @@
100392100382
int nRef = pNC->nRef;
100393100383
testcase( pNC->ncFlags & NC_IsCheck );
100394100384
testcase( pNC->ncFlags & NC_PartIdx );
100395100385
testcase( pNC->ncFlags & NC_IdxExpr );
100396100386
testcase( pNC->ncFlags & NC_GenCol );
100397
- sqlite3ResolveNotValid(pParse, pNC, "subqueries",
100398
- NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr);
100399
- sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
100387
+ if( pNC->ncFlags & NC_SelfRef ){
100388
+ notValidImpl(pParse, pNC, "subqueries", pExpr);
100389
+ }else{
100390
+ sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
100391
+ }
100400100392
assert( pNC->nRef>=nRef );
100401100393
if( nRef!=pNC->nRef ){
100402100394
ExprSetProperty(pExpr, EP_VarSelect);
100403100395
pNC->ncFlags |= NC_VarSelect;
100404100396
}
@@ -101322,11 +101314,11 @@
101322101314
static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree);
101323101315
101324101316
/*
101325101317
** Return the affinity character for a single column of a table.
101326101318
*/
101327
-SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){
101319
+SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table *pTab, int iCol){
101328101320
assert( iCol<pTab->nCol );
101329101321
return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER;
101330101322
}
101331101323
101332101324
/*
@@ -101393,11 +101385,11 @@
101393101385
**
101394101386
** If a memory allocation error occurs, that fact is recorded in pParse->db
101395101387
** and the pExpr parameter is returned unchanged.
101396101388
*/
101397101389
SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(
101398
- Parse *pParse, /* Parsing context */
101390
+ const Parse *pParse, /* Parsing context */
101399101391
Expr *pExpr, /* Add the "COLLATE" clause to this expression */
101400101392
const Token *pCollName, /* Name of collating sequence */
101401101393
int dequote /* True to dequote pCollName */
101402101394
){
101403101395
if( pCollName->n>0 ){
@@ -101408,11 +101400,15 @@
101408101400
pExpr = pNew;
101409101401
}
101410101402
}
101411101403
return pExpr;
101412101404
}
101413
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){
101405
+SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(
101406
+ const Parse *pParse, /* Parsing context */
101407
+ Expr *pExpr, /* Add the "COLLATE" clause to this expression */
101408
+ const char *zC /* The collating sequence name */
101409
+){
101414101410
Token s;
101415101411
assert( zC!=0 );
101416101412
sqlite3TokenInit(&s, (char*)zC);
101417101413
return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0);
101418101414
}
@@ -101710,21 +101706,21 @@
101710101706
** columns of result. Every TK_VECTOR node is an vector because the
101711101707
** parser will not generate a TK_VECTOR with fewer than two entries.
101712101708
** But a TK_SELECT might be either a vector or a scalar. It is only
101713101709
** considered a vector if it has two or more result columns.
101714101710
*/
101715
-SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){
101711
+SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr){
101716101712
return sqlite3ExprVectorSize(pExpr)>1;
101717101713
}
101718101714
101719101715
/*
101720101716
** If the expression passed as the only argument is of type TK_VECTOR
101721101717
** return the number of expressions in the vector. Or, if the expression
101722101718
** is a sub-select, return the number of columns in the sub-select. For
101723101719
** any other type of expression, return 1.
101724101720
*/
101725
-SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
101721
+SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr){
101726101722
u8 op = pExpr->op;
101727101723
if( op==TK_REGISTER ) op = pExpr->op2;
101728101724
if( op==TK_VECTOR ){
101729101725
return pExpr->x.pList->nExpr;
101730101726
}else if( op==TK_SELECT ){
@@ -101813,13 +101809,20 @@
101813101809
pRet->iTable = nField;
101814101810
pRet->iColumn = iField;
101815101811
pRet->pLeft = pVector;
101816101812
}
101817101813
}else{
101818
- if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr;
101814
+ if( pVector->op==TK_VECTOR ){
101815
+ Expr **ppVector = &pVector->x.pList->a[iField].pExpr;
101816
+ pVector = *ppVector;
101817
+ if( IN_RENAME_OBJECT ){
101818
+ /* This must be a vector UPDATE inside a trigger */
101819
+ *ppVector = 0;
101820
+ return pVector;
101821
+ }
101822
+ }
101819101823
pRet = sqlite3ExprDup(pParse->db, pVector, 0);
101820
- sqlite3RenameTokenRemap(pParse, pRet, pVector);
101821101824
}
101822101825
return pRet;
101823101826
}
101824101827
101825101828
/*
@@ -102008,27 +102011,27 @@
102008102011
**
102009102012
** If this maximum height is greater than the current value pointed
102010102013
** to by pnHeight, the second parameter, then set *pnHeight to that
102011102014
** value.
102012102015
*/
102013
-static void heightOfExpr(Expr *p, int *pnHeight){
102016
+static void heightOfExpr(const Expr *p, int *pnHeight){
102014102017
if( p ){
102015102018
if( p->nHeight>*pnHeight ){
102016102019
*pnHeight = p->nHeight;
102017102020
}
102018102021
}
102019102022
}
102020
-static void heightOfExprList(ExprList *p, int *pnHeight){
102023
+static void heightOfExprList(const ExprList *p, int *pnHeight){
102021102024
if( p ){
102022102025
int i;
102023102026
for(i=0; i<p->nExpr; i++){
102024102027
heightOfExpr(p->a[i].pExpr, pnHeight);
102025102028
}
102026102029
}
102027102030
}
102028
-static void heightOfSelect(Select *pSelect, int *pnHeight){
102029
- Select *p;
102031
+static void heightOfSelect(const Select *pSelect, int *pnHeight){
102032
+ const Select *p;
102030102033
for(p=pSelect; p; p=p->pPrior){
102031102034
heightOfExpr(p->pWhere, pnHeight);
102032102035
heightOfExpr(p->pHaving, pnHeight);
102033102036
heightOfExpr(p->pLimit, pnHeight);
102034102037
heightOfExprList(p->pEList, pnHeight);
@@ -102076,11 +102079,11 @@
102076102079
102077102080
/*
102078102081
** Return the maximum height of any expression tree referenced
102079102082
** by the select statement passed as an argument.
102080102083
*/
102081
-SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
102084
+SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *p){
102082102085
int nHeight = 0;
102083102086
heightOfSelect(p, &nHeight);
102084102087
return nHeight;
102085102088
}
102086102089
#else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */
@@ -102329,11 +102332,11 @@
102329102332
** arguments.
102330102333
*/
102331102334
SQLITE_PRIVATE Expr *sqlite3ExprFunction(
102332102335
Parse *pParse, /* Parsing context */
102333102336
ExprList *pList, /* Argument list */
102334
- Token *pToken, /* Name of the function */
102337
+ const Token *pToken, /* Name of the function */
102335102338
int eDistinct /* SF_Distinct or SF_ALL or 0 */
102336102339
){
102337102340
Expr *pNew;
102338102341
sqlite3 *db = pParse->db;
102339102342
assert( pToken );
@@ -102367,12 +102370,12 @@
102367102370
**
102368102371
** If the function is not usable, create an error.
102369102372
*/
102370102373
SQLITE_PRIVATE void sqlite3ExprFunctionUsable(
102371102374
Parse *pParse, /* Parsing and code generating context */
102372
- Expr *pExpr, /* The function invocation */
102373
- FuncDef *pDef /* The function being invoked */
102375
+ const Expr *pExpr, /* The function invocation */
102376
+ const FuncDef *pDef /* The function being invoked */
102374102377
){
102375102378
assert( !IN_RENAME_OBJECT );
102376102379
assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 );
102377102380
if( ExprHasProperty(pExpr, EP_FromDDL) ){
102378102381
if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0
@@ -102548,11 +102551,11 @@
102548102551
/*
102549102552
** Return the number of bytes allocated for the expression structure
102550102553
** passed as the first argument. This is always one of EXPR_FULLSIZE,
102551102554
** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE.
102552102555
*/
102553
-static int exprStructSize(Expr *p){
102556
+static int exprStructSize(const Expr *p){
102554102557
if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE;
102555102558
if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE;
102556102559
return EXPR_FULLSIZE;
102557102560
}
102558102561
@@ -102588,11 +102591,11 @@
102588102591
** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal
102589102592
** to reduce a pristine expression tree from the parser. The implementation
102590102593
** of dupedExprStructSize() contain multiple assert() statements that attempt
102591102594
** to enforce this constraint.
102592102595
*/
102593
-static int dupedExprStructSize(Expr *p, int flags){
102596
+static int dupedExprStructSize(const Expr *p, int flags){
102594102597
int nSize;
102595102598
assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */
102596102599
assert( EXPR_FULLSIZE<=0xfff );
102597102600
assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 );
102598102601
if( 0==flags || p->op==TK_SELECT_COLUMN
@@ -102619,11 +102622,11 @@
102619102622
/*
102620102623
** This function returns the space in bytes required to store the copy
102621102624
** of the Expr structure and a copy of the Expr.u.zToken string (if that
102622102625
** string is defined.)
102623102626
*/
102624
-static int dupedExprNodeSize(Expr *p, int flags){
102627
+static int dupedExprNodeSize(const Expr *p, int flags){
102625102628
int nByte = dupedExprStructSize(p, flags) & 0xfff;
102626102629
if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
102627102630
nByte += sqlite3Strlen30NN(p->u.zToken)+1;
102628102631
}
102629102632
return ROUND8(nByte);
@@ -102640,11 +102643,11 @@
102640102643
** If the EXPRDUP_REDUCE flag is set, then the return value includes
102641102644
** space to duplicate all Expr nodes in the tree formed by Expr.pLeft
102642102645
** and Expr.pRight variables (but not for any structures pointed to or
102643102646
** descended from the Expr.x.pList or Expr.x.pSelect variables).
102644102647
*/
102645
-static int dupedExprSize(Expr *p, int flags){
102648
+static int dupedExprSize(const Expr *p, int flags){
102646102649
int nByte = 0;
102647102650
if( p ){
102648102651
nByte = dupedExprNodeSize(p, flags);
102649102652
if( flags&EXPRDUP_REDUCE ){
102650102653
nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags);
@@ -102659,11 +102662,11 @@
102659102662
** to store the copy of expression p, the copies of p->u.zToken
102660102663
** (if applicable), and the copies of the p->pLeft and p->pRight expressions,
102661102664
** if any. Before returning, *pzBuffer is set to the first byte past the
102662102665
** portion of the buffer copied into by this function.
102663102666
*/
102664
-static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
102667
+static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
102665102668
Expr *pNew; /* Value to return */
102666102669
u8 *zAlloc; /* Memory space from which to build Expr object */
102667102670
u32 staticFlag; /* EP_Static if space not obtained from malloc */
102668102671
102669102672
assert( db!=0 );
@@ -102840,17 +102843,18 @@
102840102843
** The flags parameter contains a combination of the EXPRDUP_XXX flags.
102841102844
** If the EXPRDUP_REDUCE flag is set, then the structure returned is a
102842102845
** truncated version of the usual Expr structure that will be stored as
102843102846
** part of the in-memory representation of the database schema.
102844102847
*/
102845
-SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){
102848
+SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){
102846102849
assert( flags==0 || flags==EXPRDUP_REDUCE );
102847102850
return p ? exprDup(db, p, flags, 0) : 0;
102848102851
}
102849
-SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){
102852
+SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){
102850102853
ExprList *pNew;
102851
- struct ExprList_item *pItem, *pOldItem;
102854
+ struct ExprList_item *pItem;
102855
+ const struct ExprList_item *pOldItem;
102852102856
int i;
102853102857
Expr *pPriorSelectColOld = 0;
102854102858
Expr *pPriorSelectColNew = 0;
102855102859
assert( db!=0 );
102856102860
if( p==0 ) return 0;
@@ -102898,11 +102902,11 @@
102898102902
** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes
102899102903
** called with a NULL argument.
102900102904
*/
102901102905
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \
102902102906
|| !defined(SQLITE_OMIT_SUBQUERY)
102903
-SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
102907
+SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){
102904102908
SrcList *pNew;
102905102909
int i;
102906102910
int nByte;
102907102911
assert( db!=0 );
102908102912
if( p==0 ) return 0;
@@ -102910,11 +102914,11 @@
102910102914
pNew = sqlite3DbMallocRawNN(db, nByte );
102911102915
if( pNew==0 ) return 0;
102912102916
pNew->nSrc = pNew->nAlloc = p->nSrc;
102913102917
for(i=0; i<p->nSrc; i++){
102914102918
SrcItem *pNewItem = &pNew->a[i];
102915
- SrcItem *pOldItem = &p->a[i];
102919
+ const SrcItem *pOldItem = &p->a[i];
102916102920
Table *pTab;
102917102921
pNewItem->pSchema = pOldItem->pSchema;
102918102922
pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
102919102923
pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102920102924
pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
@@ -102942,11 +102946,11 @@
102942102946
pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
102943102947
pNewItem->colUsed = pOldItem->colUsed;
102944102948
}
102945102949
return pNew;
102946102950
}
102947
-SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){
102951
+SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){
102948102952
IdList *pNew;
102949102953
int i;
102950102954
assert( db!=0 );
102951102955
if( p==0 ) return 0;
102952102956
pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
@@ -102966,15 +102970,15 @@
102966102970
pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102967102971
pNewItem->idx = pOldItem->idx;
102968102972
}
102969102973
return pNew;
102970102974
}
102971
-SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){
102975
+SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){
102972102976
Select *pRet = 0;
102973102977
Select *pNext = 0;
102974102978
Select **pp = &pRet;
102975
- Select *p;
102979
+ const Select *p;
102976102980
102977102981
assert( db!=0 );
102978102982
for(p=pDup; p; p=p->pPrior){
102979102983
Select *pNew = sqlite3DbMallocRawNN(db, sizeof(*p) );
102980102984
if( pNew==0 ) break;
@@ -103211,11 +103215,11 @@
103211103215
** is set.
103212103216
*/
103213103217
SQLITE_PRIVATE void sqlite3ExprListSetName(
103214103218
Parse *pParse, /* Parsing context */
103215103219
ExprList *pList, /* List to which to add the span. */
103216
- Token *pName, /* Name to be added */
103220
+ const Token *pName, /* Name to be added */
103217103221
int dequote /* True to cause the name to be dequoted */
103218103222
){
103219103223
assert( pList!=0 || pParse->db->mallocFailed!=0 );
103220103224
assert( pParse->eParseMode!=PARSE_MODE_UNMAP || dequote==0 );
103221103225
if( pList ){
@@ -103229,11 +103233,11 @@
103229103233
/* If dequote==0, then pName->z does not point to part of a DDL
103230103234
** statement handled by the parser. And so no token need be added
103231103235
** to the token-map. */
103232103236
sqlite3Dequote(pItem->zEName);
103233103237
if( IN_RENAME_OBJECT ){
103234
- sqlite3RenameTokenMap(pParse, (void*)pItem->zEName, pName);
103238
+ sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName);
103235103239
}
103236103240
}
103237103241
}
103238103242
}
103239103243
@@ -103657,11 +103661,11 @@
103657103661
** If the expression p codes a constant integer that is small enough
103658103662
** to fit in a 32-bit integer, return 1 and put the value of the integer
103659103663
** in *pValue. If the expression is not an integer or if it is too big
103660103664
** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
103661103665
*/
103662
-SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
103666
+SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue){
103663103667
int rc = 0;
103664103668
if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
103665103669
103666103670
/* If an expression is an integer literal that fits in a signed 32-bit
103667103671
** integer, then the EP_IntValue flag will have already been set */
@@ -103790,11 +103794,11 @@
103790103794
** a pointer to the SELECT statement. If pX is not a SELECT statement,
103791103795
** or if the SELECT statement needs to be manifested into a transient
103792103796
** table, then return NULL.
103793103797
*/
103794103798
#ifndef SQLITE_OMIT_SUBQUERY
103795
-static Select *isCandidateForInOpt(Expr *pX){
103799
+static Select *isCandidateForInOpt(const Expr *pX){
103796103800
Select *p;
103797103801
SrcList *pSrc;
103798103802
ExprList *pEList;
103799103803
Table *pTab;
103800103804
int i;
@@ -104168,11 +104172,11 @@
104168104172
** the affinities to be used for each column of the comparison.
104169104173
**
104170104174
** It is the responsibility of the caller to ensure that the returned
104171104175
** string is eventually freed using sqlite3DbFree().
104172104176
*/
104173
-static char *exprINAffinity(Parse *pParse, Expr *pExpr){
104177
+static char *exprINAffinity(Parse *pParse, const Expr *pExpr){
104174104178
Expr *pLeft = pExpr->pLeft;
104175104179
int nVal = sqlite3ExprVectorSize(pLeft);
104176104180
Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0;
104177104181
char *zRet;
104178104182
@@ -106087,11 +106091,11 @@
106087106091
assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
106088106092
if( pParse->pVdbe==0 ) return;
106089106093
inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
106090106094
if( inReg!=target ){
106091106095
u8 op;
106092
- if( ExprHasProperty(pExpr,EP_Subquery) ){
106096
+ if( ALWAYS(pExpr) && ExprHasProperty(pExpr,EP_Subquery) ){
106093106097
op = OP_Copy;
106094106098
}else{
106095106099
op = OP_SCopy;
106096106100
}
106097106101
sqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target);
@@ -106625,11 +106629,15 @@
106625106629
** Additionally, if pExpr is a simple SQL value and the value is the
106626106630
** same as that currently bound to variable pVar, non-zero is returned.
106627106631
** Otherwise, if the values are not the same or if pExpr is not a simple
106628106632
** SQL value, zero is returned.
106629106633
*/
106630
-static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){
106634
+static int exprCompareVariable(
106635
+ const Parse *pParse,
106636
+ const Expr *pVar,
106637
+ const Expr *pExpr
106638
+){
106631106639
int res = 0;
106632106640
int iVar;
106633106641
sqlite3_value *pL, *pR = 0;
106634106642
106635106643
sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
@@ -106677,11 +106685,16 @@
106677106685
** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
106678106686
** If pParse is NULL (the normal case) then any TK_VARIABLE term in
106679106687
** Argument pParse should normally be NULL. If it is not NULL and pA or
106680106688
** pB causes a return value of 2.
106681106689
*/
106682
-SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){
106690
+SQLITE_PRIVATE int sqlite3ExprCompare(
106691
+ const Parse *pParse,
106692
+ const Expr *pA,
106693
+ const Expr *pB,
106694
+ int iTab
106695
+){
106683106696
u32 combinedFlags;
106684106697
if( pA==0 || pB==0 ){
106685106698
return pB==pA ? 0 : 2;
106686106699
}
106687106700
if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
@@ -106761,11 +106774,11 @@
106761106774
** a malfunction will result.
106762106775
**
106763106776
** Two NULL pointers are considered to be the same. But a NULL pointer
106764106777
** always differs from a non-NULL pointer.
106765106778
*/
106766
-SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){
106779
+SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){
106767106780
int i;
106768106781
if( pA==0 && pB==0 ) return 0;
106769106782
if( pA==0 || pB==0 ) return 1;
106770106783
if( pA->nExpr!=pB->nExpr ) return 1;
106771106784
for(i=0; i<pA->nExpr; i++){
@@ -106780,11 +106793,11 @@
106780106793
106781106794
/*
106782106795
** Like sqlite3ExprCompare() except COLLATE operators at the top-level
106783106796
** are ignored.
106784106797
*/
106785
-SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){
106798
+SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){
106786106799
return sqlite3ExprCompare(0,
106787106800
sqlite3ExprSkipCollateAndLikely(pA),
106788106801
sqlite3ExprSkipCollateAndLikely(pB),
106789106802
iTab);
106790106803
}
@@ -106794,13 +106807,13 @@
106794106807
**
106795106808
** Or if seenNot is true, return non-zero if Expr p can only be
106796106809
** non-NULL if pNN is not NULL
106797106810
*/
106798106811
static int exprImpliesNotNull(
106799
- Parse *pParse, /* Parsing context */
106800
- Expr *p, /* The expression to be checked */
106801
- Expr *pNN, /* The expression that is NOT NULL */
106812
+ const Parse *pParse,/* Parsing context */
106813
+ const Expr *p, /* The expression to be checked */
106814
+ const Expr *pNN, /* The expression that is NOT NULL */
106802106815
int iTab, /* Table being evaluated */
106803106816
int seenNot /* Return true only if p can be any non-NULL value */
106804106817
){
106805106818
assert( p );
106806106819
assert( pNN );
@@ -106889,11 +106902,16 @@
106889106902
**
106890106903
** When in doubt, return false. Returning true might give a performance
106891106904
** improvement. Returning false might cause a performance reduction, but
106892106905
** it will always give the correct answer and is hence always safe.
106893106906
*/
106894
-SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){
106907
+SQLITE_PRIVATE int sqlite3ExprImpliesExpr(
106908
+ const Parse *pParse,
106909
+ const Expr *pE1,
106910
+ const Expr *pE2,
106911
+ int iTab
106912
+){
106895106913
if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){
106896106914
return 1;
106897106915
}
106898106916
if( pE2->op==TK_OR
106899106917
&& (sqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab)
@@ -108220,11 +108238,11 @@
108220108238
** After the parse finishes, renameTokenFind() routine can be used
108221108239
** to look up the actual token value that created some element in
108222108240
** the parse tree.
108223108241
*/
108224108242
struct RenameToken {
108225
- void *p; /* Parse tree element created by token t */
108243
+ const void *p; /* Parse tree element created by token t */
108226108244
Token t; /* The token that created parse tree element p */
108227108245
RenameToken *pNext; /* Next is a list of all RenameToken objects */
108228108246
};
108229108247
108230108248
/*
@@ -108262,13 +108280,13 @@
108262108280
** if( x==y ) ...
108263108281
**
108264108282
** Technically, as x no longer points into a valid object or to the byte
108265108283
** following a valid object, it may not be used in comparison operations.
108266108284
*/
108267
-static void renameTokenCheckAll(Parse *pParse, void *pPtr){
108285
+static void renameTokenCheckAll(Parse *pParse, const void *pPtr){
108268108286
if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){
108269
- RenameToken *p;
108287
+ const RenameToken *p;
108270108288
u8 i = 0;
108271108289
for(p=pParse->pRename; p; p=p->pNext){
108272108290
if( p->p ){
108273108291
assert( p->p!=pPtr );
108274108292
i += *(u8*)(p->p);
@@ -108290,11 +108308,15 @@
108290108308
**
108291108309
** The pPtr argument is returned so that this routine can be used
108292108310
** with tail recursion in tokenExpr() routine, for a small performance
108293108311
** improvement.
108294108312
*/
108295
-SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){
108313
+SQLITE_PRIVATE const void *sqlite3RenameTokenMap(
108314
+ Parse *pParse,
108315
+ const void *pPtr,
108316
+ const Token *pToken
108317
+){
108296108318
RenameToken *pNew;
108297108319
assert( pPtr || pParse->db->mallocFailed );
108298108320
renameTokenCheckAll(pParse, pPtr);
108299108321
if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){
108300108322
pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken));
@@ -108312,11 +108334,11 @@
108312108334
/*
108313108335
** It is assumed that there is already a RenameToken object associated
108314108336
** with parse tree element pFrom. This function remaps the associated token
108315108337
** to parse tree element pTo.
108316108338
*/
108317
-SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){
108339
+SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){
108318108340
RenameToken *p;
108319108341
renameTokenCheckAll(pParse, pTo);
108320108342
for(p=pParse->pRename; p; p=p->pNext){
108321108343
if( p->p==pFrom ){
108322108344
p->p = pTo;
@@ -108328,11 +108350,12 @@
108328108350
/*
108329108351
** Walker callback used by sqlite3RenameExprUnmap().
108330108352
*/
108331108353
static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){
108332108354
Parse *pParse = pWalker->pParse;
108333
- sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr);
108355
+ sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr);
108356
+ sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab);
108334108357
return WRC_Continue;
108335108358
}
108336108359
108337108360
/*
108338108361
** Iterate through the Select objects that are part of WITH clauses attached
@@ -108358,10 +108381,11 @@
108358108381
Select *p = pWith->a[i].pSelect;
108359108382
NameContext sNC;
108360108383
memset(&sNC, 0, sizeof(sNC));
108361108384
sNC.pParse = pParse;
108362108385
if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC);
108386
+ if( sNC.pParse->db->mallocFailed ) return;
108363108387
sqlite3WalkSelect(pWalker, p);
108364108388
sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols);
108365108389
}
108366108390
if( pCopy && pParse->pWith==pCopy ){
108367108391
pParse->pWith = pCopy->pOuter;
@@ -108372,16 +108396,16 @@
108372108396
/*
108373108397
** Unmap all tokens in the IdList object passed as the second argument.
108374108398
*/
108375108399
static void unmapColumnIdlistNames(
108376108400
Parse *pParse,
108377
- IdList *pIdList
108401
+ const IdList *pIdList
108378108402
){
108379108403
if( pIdList ){
108380108404
int ii;
108381108405
for(ii=0; ii<pIdList->nId; ii++){
108382
- sqlite3RenameTokenRemap(pParse, 0, (void*)pIdList->a[ii].zName);
108406
+ sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName);
108383108407
}
108384108408
}
108385108409
}
108386108410
108387108411
/*
@@ -108389,13 +108413,11 @@
108389108413
*/
108390108414
static int renameUnmapSelectCb(Walker *pWalker, Select *p){
108391108415
Parse *pParse = pWalker->pParse;
108392108416
int i;
108393108417
if( pParse->nErr ) return WRC_Abort;
108394
- if( p->selFlags & (SF_View|SF_CopyCte) ){
108395
- testcase( p->selFlags & SF_View );
108396
- testcase( p->selFlags & SF_CopyCte );
108418
+ if( NEVER(p->selFlags & (SF_View|SF_CopyCte)) ){
108397108419
return WRC_Prune;
108398108420
}
108399108421
if( ALWAYS(p->pEList) ){
108400108422
ExprList *pList = p->pEList;
108401108423
for(i=0; i<pList->nExpr; i++){
@@ -108406,11 +108428,11 @@
108406108428
}
108407108429
if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */
108408108430
SrcList *pSrc = p->pSrc;
108409108431
for(i=0; i<pSrc->nSrc; i++){
108410108432
sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName);
108411
- if( sqlite3WalkExpr(pWalker, pSrc->a[i].pOn) ) return WRC_Abort;
108433
+ sqlite3WalkExpr(pWalker, pSrc->a[i].pOn);
108412108434
unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing);
108413108435
}
108414108436
}
108415108437
108416108438
renameWalkWith(pWalker, p);
@@ -108474,11 +108496,11 @@
108474108496
** the list maintained by the RenameCtx object.
108475108497
*/
108476108498
static RenameToken *renameTokenFind(
108477108499
Parse *pParse,
108478108500
struct RenameCtx *pCtx,
108479
- void *pPtr
108501
+ const void *pPtr
108480108502
){
108481108503
RenameToken **pp;
108482108504
if( NEVER(pPtr==0) ){
108483108505
return 0;
108484108506
}
@@ -108593,22 +108615,22 @@
108593108615
** to the RenameCtx pCtx.
108594108616
*/
108595108617
static void renameColumnElistNames(
108596108618
Parse *pParse,
108597108619
RenameCtx *pCtx,
108598
- ExprList *pEList,
108620
+ const ExprList *pEList,
108599108621
const char *zOld
108600108622
){
108601108623
if( pEList ){
108602108624
int i;
108603108625
for(i=0; i<pEList->nExpr; i++){
108604
- char *zName = pEList->a[i].zEName;
108626
+ const char *zName = pEList->a[i].zEName;
108605108627
if( ALWAYS(pEList->a[i].eEName==ENAME_NAME)
108606108628
&& ALWAYS(zName!=0)
108607108629
&& 0==sqlite3_stricmp(zName, zOld)
108608108630
){
108609
- renameTokenFind(pParse, pCtx, (void*)zName);
108631
+ renameTokenFind(pParse, pCtx, (const void*)zName);
108610108632
}
108611108633
}
108612108634
}
108613108635
}
108614108636
@@ -108618,19 +108640,19 @@
108618108640
** from Parse object pParse and add it to the RenameCtx pCtx.
108619108641
*/
108620108642
static void renameColumnIdlistNames(
108621108643
Parse *pParse,
108622108644
RenameCtx *pCtx,
108623
- IdList *pIdList,
108645
+ const IdList *pIdList,
108624108646
const char *zOld
108625108647
){
108626108648
if( pIdList ){
108627108649
int i;
108628108650
for(i=0; i<pIdList->nId; i++){
108629
- char *zName = pIdList->a[i].zName;
108651
+ const char *zName = pIdList->a[i].zName;
108630108652
if( 0==sqlite3_stricmp(zName, zOld) ){
108631
- renameTokenFind(pParse, pCtx, (void*)zName);
108653
+ renameTokenFind(pParse, pCtx, (const void*)zName);
108632108654
}
108633108655
}
108634108656
}
108635108657
}
108636108658
@@ -109326,11 +109348,11 @@
109326109348
return;
109327109349
}
109328109350
109329109351
static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){
109330109352
if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){
109331
- renameTokenFind(pWalker->pParse, pWalker->u.pRename, (void*)pExpr);
109353
+ renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr);
109332109354
}
109333109355
return WRC_Continue;
109334109356
}
109335109357
109336109358
/*
@@ -109596,11 +109618,11 @@
109596109618
** ALTER TABLE pSrc DROP COLUMN pName
109597109619
**
109598109620
** statement. Argument pSrc contains the possibly qualified name of the
109599109621
** table being edited, and token pName the name of the column to drop.
109600109622
*/
109601
-SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){
109623
+SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){
109602109624
sqlite3 *db = pParse->db; /* Database handle */
109603109625
Table *pTab; /* Table to modify */
109604109626
int iDb; /* Index of db containing pTab in aDb[] */
109605109627
const char *zDb; /* Database containing pTab ("main" etc.) */
109606109628
char *zCol = 0; /* Name of column to drop */
@@ -110181,11 +110203,10 @@
110181110203
n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */
110182110204
+ sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */
110183110205
+ sizeof(tRowcnt)*3*nColUp*(nCol+mxSample);
110184110206
}
110185110207
#endif
110186
- db = sqlite3_context_db_handle(context);
110187110208
p = sqlite3DbMallocZero(db, n);
110188110209
if( p==0 ){
110189110210
sqlite3_result_error_nomem(context);
110190110211
return;
110191110212
}
@@ -110600,32 +110621,23 @@
110600110621
** If D is the count of distinct values and K is the total number of
110601110622
** rows, then each estimate is computed as:
110602110623
**
110603110624
** I = (K+D-1)/D
110604110625
*/
110605
- char *z;
110606
- int i;
110607
-
110608
- char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 );
110609
- if( zRet==0 ){
110610
- sqlite3_result_error_nomem(context);
110611
- return;
110612
- }
110613
-
110614
- sqlite3_snprintf(24, zRet, "%llu",
110615
- p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow);
110616
- z = zRet + sqlite3Strlen30(zRet);
110626
+ sqlite3_str sStat; /* Text of the constructed "stat" line */
110627
+ int i; /* Loop counter */
110628
+
110629
+ sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100);
110630
+ sqlite3_str_appendf(&sStat, "%llu",
110631
+ p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow);
110617110632
for(i=0; i<p->nKeyCol; i++){
110618110633
u64 nDistinct = p->current.anDLt[i] + 1;
110619110634
u64 iVal = (p->nRow + nDistinct - 1) / nDistinct;
110620
- sqlite3_snprintf(24, z, " %llu", iVal);
110621
- z += sqlite3Strlen30(z);
110635
+ sqlite3_str_appendf(&sStat, " %llu", iVal);
110622110636
assert( p->current.anEq[i] );
110623110637
}
110624
- assert( z[0]=='\0' && z>zRet );
110625
-
110626
- sqlite3_result_text(context, zRet, -1, sqlite3_free);
110638
+ sqlite3ResultStrAccum(context, &sStat);
110627110639
}
110628110640
#ifdef SQLITE_ENABLE_STAT4
110629110641
else if( eCall==STAT_GET_ROWID ){
110630110642
if( p->iGet<0 ){
110631110643
samplePushPrevious(p, 0);
@@ -110640,10 +110652,12 @@
110640110652
SQLITE_TRANSIENT);
110641110653
}
110642110654
}
110643110655
}else{
110644110656
tRowcnt *aCnt = 0;
110657
+ sqlite3_str sStat;
110658
+ int i;
110645110659
110646110660
assert( p->iGet<p->nSample );
110647110661
switch( eCall ){
110648110662
case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break;
110649110663
case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break;
@@ -110651,27 +110665,16 @@
110651110665
aCnt = p->a[p->iGet].anDLt;
110652110666
p->iGet++;
110653110667
break;
110654110668
}
110655110669
}
110656
-
110657
- {
110658
- char *zRet = sqlite3MallocZero(p->nCol * 25);
110659
- if( zRet==0 ){
110660
- sqlite3_result_error_nomem(context);
110661
- }else{
110662
- int i;
110663
- char *z = zRet;
110664
- for(i=0; i<p->nCol; i++){
110665
- sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]);
110666
- z += sqlite3Strlen30(z);
110667
- }
110668
- assert( z[0]=='\0' && z>zRet );
110669
- z[-1] = '\0';
110670
- sqlite3_result_text(context, zRet, -1, sqlite3_free);
110671
- }
110672
- }
110670
+ sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100);
110671
+ for(i=0; i<p->nCol; i++){
110672
+ sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]);
110673
+ }
110674
+ if( sStat.nChar ) sStat.nChar--;
110675
+ sqlite3ResultStrAccum(context, &sStat);
110673110676
}
110674110677
#endif /* SQLITE_ENABLE_STAT4 */
110675110678
#ifndef SQLITE_DEBUG
110676110679
UNUSED_PARAMETER( argc );
110677110680
#endif
@@ -111588,13 +111591,16 @@
111588111591
** Load content from the sqlite_stat4 table into
111589111592
** the Index.aSample[] arrays of all indices.
111590111593
*/
111591111594
static int loadStat4(sqlite3 *db, const char *zDb){
111592111595
int rc = SQLITE_OK; /* Result codes from subroutines */
111596
+ const Table *pStat4;
111593111597
111594111598
assert( db->lookaside.bDisable );
111595
- if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){
111599
+ if( (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0
111600
+ && IsOrdinaryTable(pStat4)
111601
+ ){
111596111602
rc = loadStatTbl(db,
111597111603
"SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx",
111598111604
"SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4",
111599111605
zDb
111600111606
);
@@ -111627,10 +111633,11 @@
111627111633
analysisInfo sInfo;
111628111634
HashElem *i;
111629111635
char *zSql;
111630111636
int rc = SQLITE_OK;
111631111637
Schema *pSchema = db->aDb[iDb].pSchema;
111638
+ const Table *pStat1;
111632111639
111633111640
assert( iDb>=0 && iDb<db->nDb );
111634111641
assert( db->aDb[iDb].pBt!=0 );
111635111642
111636111643
/* Clear any prior statistics */
@@ -111649,11 +111656,13 @@
111649111656
}
111650111657
111651111658
/* Load new statistics out of the sqlite_stat1 table */
111652111659
sInfo.db = db;
111653111660
sInfo.zDatabase = db->aDb[iDb].zDbSName;
111654
- if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){
111661
+ if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase))
111662
+ && IsOrdinaryTable(pStat1)
111663
+ ){
111655111664
zSql = sqlite3MPrintf(db,
111656111665
"SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
111657111666
if( zSql==0 ){
111658111667
rc = SQLITE_NOMEM_BKPT;
111659111668
}else{
@@ -113453,14 +113462,14 @@
113453113462
**
113454113463
** Tokens are often just pointers into the original SQL text and so
113455113464
** are not \000 terminated and are not persistent. The returned string
113456113465
** is \000 terminated and is persistent.
113457113466
*/
113458
-SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){
113467
+SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){
113459113468
char *zName;
113460113469
if( pName ){
113461
- zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n);
113470
+ zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n);
113462113471
sqlite3Dequote(zName);
113463113472
}else{
113464113473
zName = 0;
113465113474
}
113466113475
return zName;
@@ -116947,11 +116956,11 @@
116947116956
if( pTab ){
116948116957
/* Ensure all REPLACE indexes on pTab are at the end of the pIndex list.
116949116958
** The list was already ordered when this routine was entered, so at this
116950116959
** point at most a single index (the newly added index) will be out of
116951116960
** order. So we have to reorder at most one index. */
116952
- Index **ppFrom = &pTab->pIndex;
116961
+ Index **ppFrom;
116953116962
Index *pThis;
116954116963
for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){
116955116964
Index *pNext;
116956116965
if( pThis->onError!=OE_Replace ) continue;
116957116966
while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){
@@ -121358,101 +121367,167 @@
121358121367
minMaxValueFinalize(context, 0);
121359121368
}
121360121369
121361121370
/*
121362121371
** group_concat(EXPR, ?SEPARATOR?)
121372
+**
121373
+** The SEPARATOR goes before the EXPR string. This is tragic. The
121374
+** groupConcatInverse() implementation would have been easier if the
121375
+** SEPARATOR were appended after EXPR. And the order is undocumented,
121376
+** so we could change it, in theory. But the old behavior has been
121377
+** around for so long that we dare not, for fear of breaking something.
121363121378
*/
121379
+typedef struct {
121380
+ StrAccum str; /* The accumulated concatenation */
121381
+#ifndef SQLITE_OMIT_WINDOWFUNC
121382
+ int nAccum; /* Number of strings presently concatenated */
121383
+ int nFirstSepLength; /* Used to detect separator length change */
121384
+ /* If pnSepLengths!=0, refs an array of inter-string separator lengths,
121385
+ ** stored as actually incorporated into presently accumulated result.
121386
+ ** (Hence, its slots in use number nAccum-1 between method calls.)
121387
+ ** If pnSepLengths==0, nFirstSepLength is the length used throughout.
121388
+ */
121389
+ int *pnSepLengths;
121390
+#endif
121391
+} GroupConcatCtx;
121392
+
121364121393
static void groupConcatStep(
121365121394
sqlite3_context *context,
121366121395
int argc,
121367121396
sqlite3_value **argv
121368121397
){
121369121398
const char *zVal;
121370
- StrAccum *pAccum;
121399
+ GroupConcatCtx *pGCC;
121371121400
const char *zSep;
121372121401
int nVal, nSep;
121373121402
assert( argc==1 || argc==2 );
121374121403
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121375
- pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
121376
-
121377
- if( pAccum ){
121404
+ pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
121405
+ if( pGCC ){
121378121406
sqlite3 *db = sqlite3_context_db_handle(context);
121379
- int firstTerm = pAccum->mxAlloc==0;
121380
- pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
121381
- if( !firstTerm ){
121382
- if( argc==2 ){
121383
- zSep = (char*)sqlite3_value_text(argv[1]);
121384
- nSep = sqlite3_value_bytes(argv[1]);
121385
- }else{
121386
- zSep = ",";
121387
- nSep = 1;
121388
- }
121389
- if( zSep ) sqlite3_str_append(pAccum, zSep, nSep);
121390
- }
121407
+ int firstTerm = pGCC->str.mxAlloc==0;
121408
+ pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
121409
+ if( argc==1 ){
121410
+ if( !firstTerm ){
121411
+ sqlite3_str_appendchar(&pGCC->str, 1, ',');
121412
+ }
121413
+#ifndef SQLITE_OMIT_WINDOWFUNC
121414
+ else{
121415
+ pGCC->nFirstSepLength = 1;
121416
+ }
121417
+#endif
121418
+ }else if( !firstTerm ){
121419
+ zSep = (char*)sqlite3_value_text(argv[1]);
121420
+ nSep = sqlite3_value_bytes(argv[1]);
121421
+ if( zSep ){
121422
+ sqlite3_str_append(&pGCC->str, zSep, nSep);
121423
+ }
121424
+#ifndef SQLITE_OMIT_WINDOWFUNC
121425
+ else{
121426
+ nSep = 0;
121427
+ }
121428
+ if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){
121429
+ int *pnsl = pGCC->pnSepLengths;
121430
+ if( pnsl == 0 ){
121431
+ /* First separator length variation seen, start tracking them. */
121432
+ pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int));
121433
+ if( pnsl!=0 ){
121434
+ int i = 0, nA = pGCC->nAccum-1;
121435
+ while( i<nA ) pnsl[i++] = pGCC->nFirstSepLength;
121436
+ }
121437
+ }else{
121438
+ pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int));
121439
+ }
121440
+ if( pnsl!=0 ){
121441
+ if( ALWAYS(pGCC->nAccum>0) ){
121442
+ pnsl[pGCC->nAccum-1] = nSep;
121443
+ }
121444
+ pGCC->pnSepLengths = pnsl;
121445
+ }else{
121446
+ sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM);
121447
+ }
121448
+ }
121449
+#endif
121450
+ }
121451
+#ifndef SQLITE_OMIT_WINDOWFUNC
121452
+ else{
121453
+ pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]);
121454
+ }
121455
+ pGCC->nAccum += 1;
121456
+#endif
121391121457
zVal = (char*)sqlite3_value_text(argv[0]);
121392121458
nVal = sqlite3_value_bytes(argv[0]);
121393
- if( zVal ) sqlite3_str_append(pAccum, zVal, nVal);
121459
+ if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal);
121394121460
}
121395121461
}
121462
+
121396121463
#ifndef SQLITE_OMIT_WINDOWFUNC
121397121464
static void groupConcatInverse(
121398121465
sqlite3_context *context,
121399121466
int argc,
121400121467
sqlite3_value **argv
121401121468
){
121402
- int n;
121403
- StrAccum *pAccum;
121469
+ GroupConcatCtx *pGCC;
121404121470
assert( argc==1 || argc==2 );
121471
+ (void)argc; /* Suppress unused parameter warning */
121405121472
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121406
- pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
121407
- /* pAccum is always non-NULL since groupConcatStep() will have always
121473
+ pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
121474
+ /* pGCC is always non-NULL since groupConcatStep() will have always
121408121475
** run frist to initialize it */
121409
- if( ALWAYS(pAccum) ){
121410
- n = sqlite3_value_bytes(argv[0]);
121411
- if( argc==2 ){
121412
- n += sqlite3_value_bytes(argv[1]);
121413
- }else{
121414
- n++;
121415
- }
121416
- if( n>=(int)pAccum->nChar ){
121417
- pAccum->nChar = 0;
121418
- }else{
121419
- pAccum->nChar -= n;
121420
- memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar);
121421
- }
121422
- if( pAccum->nChar==0 ) pAccum->mxAlloc = 0;
121476
+ if( ALWAYS(pGCC) ){
121477
+ int nVS = sqlite3_value_bytes(argv[0]);
121478
+ pGCC->nAccum -= 1;
121479
+ if( pGCC->pnSepLengths!=0 ){
121480
+ assert(pGCC->nAccum >= 0);
121481
+ if( pGCC->nAccum>0 ){
121482
+ nVS += *pGCC->pnSepLengths;
121483
+ memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1,
121484
+ (pGCC->nAccum-1)*sizeof(int));
121485
+ }
121486
+ }else{
121487
+ /* If removing single accumulated string, harmlessly over-do. */
121488
+ nVS += pGCC->nFirstSepLength;
121489
+ }
121490
+ if( nVS>=(int)pGCC->str.nChar ){
121491
+ pGCC->str.nChar = 0;
121492
+ }else{
121493
+ pGCC->str.nChar -= nVS;
121494
+ memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar);
121495
+ }
121496
+ if( pGCC->str.nChar==0 ){
121497
+ pGCC->str.mxAlloc = 0;
121498
+ sqlite3_free(pGCC->pnSepLengths);
121499
+ pGCC->pnSepLengths = 0;
121500
+ }
121423121501
}
121424121502
}
121425121503
#else
121426121504
# define groupConcatInverse 0
121427121505
#endif /* SQLITE_OMIT_WINDOWFUNC */
121428121506
static void groupConcatFinalize(sqlite3_context *context){
121429
- StrAccum *pAccum;
121430
- pAccum = sqlite3_aggregate_context(context, 0);
121431
- if( pAccum ){
121432
- if( pAccum->accError==SQLITE_TOOBIG ){
121433
- sqlite3_result_error_toobig(context);
121434
- }else if( pAccum->accError==SQLITE_NOMEM ){
121435
- sqlite3_result_error_nomem(context);
121436
- }else{
121437
- sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1,
121438
- sqlite3_free);
121439
- }
121507
+ GroupConcatCtx *pGCC
121508
+ = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
121509
+ if( pGCC ){
121510
+ sqlite3ResultStrAccum(context, &pGCC->str);
121511
+#ifndef SQLITE_OMIT_WINDOWFUNC
121512
+ sqlite3_free(pGCC->pnSepLengths);
121513
+#endif
121440121514
}
121441121515
}
121442121516
#ifndef SQLITE_OMIT_WINDOWFUNC
121443121517
static void groupConcatValue(sqlite3_context *context){
121444
- sqlite3_str *pAccum;
121445
- pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0);
121446
- if( pAccum ){
121518
+ GroupConcatCtx *pGCC
121519
+ = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
121520
+ if( pGCC ){
121521
+ StrAccum *pAccum = &pGCC->str;
121447121522
if( pAccum->accError==SQLITE_TOOBIG ){
121448121523
sqlite3_result_error_toobig(context);
121449121524
}else if( pAccum->accError==SQLITE_NOMEM ){
121450121525
sqlite3_result_error_nomem(context);
121451121526
}else{
121452121527
const char *zText = sqlite3_str_value(pAccum);
121453
- sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
121528
+ sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT);
121454121529
}
121455121530
}
121456121531
}
121457121532
#else
121458121533
# define groupConcatValue 0
@@ -131668,11 +131743,11 @@
131668131743
if( sqlite3Config.bExtraSchemaChecks ){
131669131744
corruptSchema(pData, argv, "invalid rootpage");
131670131745
}
131671131746
}
131672131747
db->init.orphanTrigger = 0;
131673
- db->init.azInit = argv;
131748
+ db->init.azInit = (const char**)argv;
131674131749
pStmt = 0;
131675131750
TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0);
131676131751
rc = db->errCode;
131677131752
assert( (rc&0xFF)==(rcp&0xFF) );
131678131753
db->init.iDb = saved_iDb;
@@ -131687,10 +131762,11 @@
131687131762
}else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){
131688131763
corruptSchema(pData, argv, sqlite3_errmsg(db));
131689131764
}
131690131765
}
131691131766
}
131767
+ db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */
131692131768
sqlite3_finalize(pStmt);
131693131769
}else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){
131694131770
corruptSchema(pData, argv, 0);
131695131771
}else{
131696131772
/* If the SQL column is blank it means this is an index that
@@ -134990,11 +135066,11 @@
134990135066
SelectDest *pDest /* What to do with query results */
134991135067
){
134992135068
SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */
134993135069
int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */
134994135070
Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */
134995
- Select *pSetup = p->pPrior; /* The setup query */
135071
+ Select *pSetup; /* The setup query */
134996135072
Select *pFirstRec; /* Left-most recursive term */
134997135073
int addrTop; /* Top of the loop */
134998135074
int addrCont, addrBreak; /* CONTINUE and BREAK addresses */
134999135075
int iCurrent = 0; /* The Current table */
135000135076
int regCurrent; /* Register holding Current table */
@@ -135074,11 +135150,10 @@
135074135150
** functions. Mark the recursive elements as UNION ALL even if they
135075135151
** are really UNION because the distinctness will be enforced by the
135076135152
** iDistinct table. pFirstRec is left pointing to the left-most
135077135153
** recursive term of the CTE.
135078135154
*/
135079
- pFirstRec = p;
135080135155
for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){
135081135156
if( pFirstRec->selFlags & SF_Aggregate ){
135082135157
sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
135083135158
goto end_of_recursive_query;
135084135159
}
@@ -138055,13 +138130,13 @@
138055138130
){
138056138131
sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
138057138132
pTab->zName);
138058138133
}
138059138134
pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0);
138060
- }else
138135
+ }
138061138136
#ifndef SQLITE_OMIT_VIRTUALTABLE
138062
- if( ALWAYS(IsVirtual(pTab))
138137
+ else if( ALWAYS(IsVirtual(pTab))
138063138138
&& pFrom->fg.fromDDL
138064138139
&& ALWAYS(pTab->u.vtab.p!=0)
138065138140
&& pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0)
138066138141
){
138067138142
sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"",
@@ -144520,10 +144595,11 @@
144520144595
VtabCtx *pCtx;
144521144596
int rc = SQLITE_OK;
144522144597
Table *pTab;
144523144598
char *zErr = 0;
144524144599
Parse sParse;
144600
+ int initBusy;
144525144601
144526144602
#ifdef SQLITE_ENABLE_API_ARMOR
144527144603
if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){
144528144604
return SQLITE_MISUSE_BKPT;
144529144605
}
@@ -144539,10 +144615,16 @@
144539144615
assert( IsVirtual(pTab) );
144540144616
144541144617
memset(&sParse, 0, sizeof(sParse));
144542144618
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
144543144619
sParse.db = db;
144620
+ /* We should never be able to reach this point while loading the
144621
+ ** schema. Nevertheless, defend against that (turn off db->init.busy)
144622
+ ** in case a bug arises. */
144623
+ assert( db->init.busy==0 );
144624
+ initBusy = db->init.busy;
144625
+ db->init.busy = 0;
144544144626
sParse.nQueryLoop = 1;
144545144627
if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr)
144546144628
&& sParse.pNewTable
144547144629
&& !db->mallocFailed
144548144630
&& IsOrdinaryTable(sParse.pNewTable)
@@ -144585,10 +144667,11 @@
144585144667
if( sParse.pVdbe ){
144586144668
sqlite3VdbeFinalize(sParse.pVdbe);
144587144669
}
144588144670
sqlite3DeleteTable(db, sParse.pNewTable);
144589144671
sqlite3ParserReset(&sParse);
144672
+ db->init.busy = initBusy;
144590144673
144591144674
assert( (rc&0xff)==rc );
144592144675
rc = sqlite3ApiExit(db, rc);
144593144676
sqlite3_mutex_leave(db->mutex);
144594144677
return rc;
@@ -154396,10 +154479,11 @@
154396154479
WhereLoop *pLoop;
154397154480
int iCur;
154398154481
int j;
154399154482
Table *pTab;
154400154483
Index *pIdx;
154484
+ WhereScan scan;
154401154485
154402154486
pWInfo = pBuilder->pWInfo;
154403154487
if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0;
154404154488
assert( pWInfo->pTabList->nSrc>=1 );
154405154489
pItem = pWInfo->pTabList->a;
@@ -154409,13 +154493,14 @@
154409154493
iCur = pItem->iCursor;
154410154494
pWC = &pWInfo->sWC;
154411154495
pLoop = pBuilder->pNew;
154412154496
pLoop->wsFlags = 0;
154413154497
pLoop->nSkip = 0;
154414
- pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0);
154498
+ pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0);
154415154499
if( pTerm ){
154416154500
testcase( pTerm->eOperator & WO_IS );
154501
+ assert( pTerm->prereqRight==0 );
154417154502
pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
154418154503
pLoop->aLTerm[0] = pTerm;
154419154504
pLoop->nLTerm = 1;
154420154505
pLoop->u.btree.nEq = 1;
154421154506
/* TUNING: Cost of a rowid lookup is 10 */
@@ -154428,11 +154513,12 @@
154428154513
|| pIdx->pPartIdxWhere!=0
154429154514
|| pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace)
154430154515
) continue;
154431154516
opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ;
154432154517
for(j=0; j<pIdx->nKeyCol; j++){
154433
- pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx);
154518
+ pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx);
154519
+ while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan);
154434154520
if( pTerm==0 ) break;
154435154521
testcase( pTerm->eOperator & WO_IS );
154436154522
pLoop->aLTerm[j] = pTerm;
154437154523
}
154438154524
if( j!=pIdx->nKeyCol ) continue;
@@ -154457,12 +154543,18 @@
154457154543
pWInfo->nRowOut = 1;
154458154544
if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr;
154459154545
if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){
154460154546
pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
154461154547
}
154548
+ if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS;
154462154549
#ifdef SQLITE_DEBUG
154463154550
pLoop->cId = '0';
154551
+#endif
154552
+#ifdef WHERETRACE_ENABLED
154553
+ if( sqlite3WhereTrace ){
154554
+ sqlite3DebugPrintf("whereShortCut() used to compute solution\n");
154555
+ }
154464154556
#endif
154465154557
return 1;
154466154558
}
154467154559
return 0;
154468154560
}
@@ -156839,11 +156931,16 @@
156839156931
/*
156840156932
** Return 0 if the two window objects are identical, 1 if they are
156841156933
** different, or 2 if it cannot be determined if the objects are identical
156842156934
** or not. Identical window objects can be processed in a single scan.
156843156935
*/
156844
-SQLITE_PRIVATE int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2, int bFilter){
156936
+SQLITE_PRIVATE int sqlite3WindowCompare(
156937
+ const Parse *pParse,
156938
+ const Window *p1,
156939
+ const Window *p2,
156940
+ int bFilter
156941
+){
156845156942
int res;
156846156943
if( NEVER(p1==0) || NEVER(p2==0) ) return 1;
156847156944
if( p1->eFrmType!=p2->eFrmType ) return 1;
156848156945
if( p1->eStart!=p2->eStart ) return 1;
156849156946
if( p1->eEnd!=p2->eEnd ) return 1;
@@ -168909,10 +169006,11 @@
168909169006
db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
168910169007
db->autoCommit = 1;
168911169008
db->nextAutovac = -1;
168912169009
db->szMmap = sqlite3GlobalConfig.szMmap;
168913169010
db->nextPagesize = 0;
169011
+ db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */
168914169012
#ifdef SQLITE_ENABLE_SORTER_MMAP
168915169013
/* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map
168916169014
** the temporary files used to do external sorts (see code in vdbesort.c)
168917169015
** is disabled. It can still be used either by defining
168918169016
** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the
@@ -170190,13 +170288,15 @@
170190170288
** passing free() a pointer that was not obtained from malloc() - it is
170191170289
** an error that we cannot easily detect but that will likely cause memory
170192170290
** corruption.
170193170291
*/
170194170292
SQLITE_API const char *sqlite3_filename_database(const char *zFilename){
170293
+ if( zFilename==0 ) return 0;
170195170294
return databaseName(zFilename);
170196170295
}
170197170296
SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){
170297
+ if( zFilename==0 ) return 0;
170198170298
zFilename = databaseName(zFilename);
170199170299
zFilename += sqlite3Strlen30(zFilename) + 1;
170200170300
while( zFilename[0] ){
170201170301
zFilename += sqlite3Strlen30(zFilename) + 1;
170202170302
zFilename += sqlite3Strlen30(zFilename) + 1;
@@ -170206,11 +170306,11 @@
170206170306
SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){
170207170307
#ifdef SQLITE_OMIT_WAL
170208170308
return 0;
170209170309
#else
170210170310
zFilename = sqlite3_filename_journal(zFilename);
170211
- zFilename += sqlite3Strlen30(zFilename) + 1;
170311
+ if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1;
170212170312
return zFilename;
170213170313
#endif
170214170314
}
170215170315
170216170316
/*
@@ -190887,11 +190987,11 @@
190887190987
}
190888190988
if( pNode->u.zJContent[0]=='-' ){ i = -i; }
190889190989
sqlite3_result_int64(pCtx, i);
190890190990
int_done:
190891190991
break;
190892
- int_as_real: i=0; /* no break */ deliberate_fall_through
190992
+ int_as_real: ; /* no break */ deliberate_fall_through
190893190993
}
190894190994
case JSON_REAL: {
190895190995
double r;
190896190996
#ifdef SQLITE_AMALGAMATION
190897190997
const char *z = pNode->u.zJContent;
@@ -195462,10 +195562,14 @@
195462195562
){
195463195563
int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64);
195464195564
xSetMapping = ((iHeight==0)?rowidWrite:parentWrite);
195465195565
if( iHeight>0 ){
195466195566
RtreeNode *pChild = nodeHashLookup(pRtree, iRowid);
195567
+ RtreeNode *p;
195568
+ for(p=pNode; p; p=p->pParent){
195569
+ if( p==pChild ) return SQLITE_CORRUPT_VTAB;
195570
+ }
195467195571
if( pChild ){
195468195572
nodeRelease(pRtree, pChild->pParent);
195469195573
nodeReference(pNode);
195470195574
pChild->pParent = pNode;
195471195575
}
@@ -206052,10 +206156,19 @@
206052206156
206053206157
/* #include "sqliteInt.h" ** Requires access to internal data structures ** */
206054206158
#if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \
206055206159
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
206056206160
206161
+/*
206162
+** The pager and btree modules arrange objects in memory so that there are
206163
+** always approximately 200 bytes of addressable memory following each page
206164
+** buffer. This way small buffer overreads caused by corrupt database pages
206165
+** do not cause undefined behaviour. This module pads each page buffer
206166
+** by the following number of bytes for the same purpose.
206167
+*/
206168
+#define DBSTAT_PAGE_PADDING_BYTES 256
206169
+
206057206170
/*
206058206171
** Page paths:
206059206172
**
206060206173
** The value of the 'path' column describes the path taken from the
206061206174
** root-node of the b-tree structure to each page. The value of the
@@ -206119,13 +206232,12 @@
206119206232
};
206120206233
206121206234
/* Size information for a single btree page */
206122206235
struct StatPage {
206123206236
u32 iPgno; /* Page number */
206124
- DbPage *pPg; /* Page content */
206237
+ u8 *aPg; /* Page buffer from sqlite3_malloc() */
206125206238
int iCell; /* Current cell */
206126
-
206127206239
char *zPath; /* Path to this page */
206128206240
206129206241
/* Variables populated by statDecodePage(): */
206130206242
u8 flags; /* Copy of flags byte */
206131206243
int nCell; /* Number of cells on page */
@@ -206333,22 +206445,29 @@
206333206445
p->nCell = 0;
206334206446
p->aCell = 0;
206335206447
}
206336206448
206337206449
static void statClearPage(StatPage *p){
206450
+ u8 *aPg = p->aPg;
206338206451
statClearCells(p);
206339
- sqlite3PagerUnref(p->pPg);
206340206452
sqlite3_free(p->zPath);
206341206453
memset(p, 0, sizeof(StatPage));
206454
+ p->aPg = aPg;
206342206455
}
206343206456
206344206457
static void statResetCsr(StatCursor *pCsr){
206345206458
int i;
206346
- sqlite3_reset(pCsr->pStmt);
206459
+ /* In some circumstances, specifically if an OOM has occurred, the call
206460
+ ** to sqlite3_reset() may cause the pager to be reset (emptied). It is
206461
+ ** important that statClearPage() is called to free any page refs before
206462
+ ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */
206347206463
for(i=0; i<ArraySize(pCsr->aPage); i++){
206348206464
statClearPage(&pCsr->aPage[i]);
206465
+ sqlite3_free(pCsr->aPage[i].aPg);
206466
+ pCsr->aPage[i].aPg = 0;
206349206467
}
206468
+ sqlite3_reset(pCsr->pStmt);
206350206469
pCsr->iPage = 0;
206351206470
sqlite3_free(pCsr->zPath);
206352206471
pCsr->zPath = 0;
206353206472
pCsr->isEof = 0;
206354206473
}
@@ -206409,11 +206528,11 @@
206409206528
int iOff;
206410206529
int nHdr;
206411206530
int isLeaf;
206412206531
int szPage;
206413206532
206414
- u8 *aData = sqlite3PagerGetData(p->pPg);
206533
+ u8 *aData = p->aPg;
206415206534
u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
206416206535
206417206536
p->flags = aHdr[0];
206418206537
if( p->flags==0x0A || p->flags==0x0D ){
206419206538
isLeaf = 1;
@@ -206480,11 +206599,11 @@
206480206599
assert( nPayload>=(u32)nLocal );
206481206600
assert( nLocal<=(nUsable-35) );
206482206601
if( nPayload>(u32)nLocal ){
206483206602
int j;
206484206603
int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
206485
- if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){
206604
+ if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){
206486206605
goto statPageIsCorrupt;
206487206606
}
206488206607
pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
206489206608
pCell->nOvfl = nOvfl;
206490206609
pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
@@ -206538,10 +206657,42 @@
206538206657
/* Not ZIPVFS: The default page size and offset */
206539206658
pCsr->szPage += sqlite3BtreeGetPageSize(pBt);
206540206659
pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
206541206660
}
206542206661
}
206662
+
206663
+/*
206664
+** Load a copy of the page data for page iPg into the buffer belonging
206665
+** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK
206666
+** if successful, or an SQLite error code otherwise.
206667
+*/
206668
+static int statGetPage(
206669
+ Btree *pBt, /* Load page from this b-tree */
206670
+ u32 iPg, /* Page number to load */
206671
+ StatPage *pPg /* Load page into this object */
206672
+){
206673
+ int pgsz = sqlite3BtreeGetPageSize(pBt);
206674
+ DbPage *pDbPage = 0;
206675
+ int rc;
206676
+
206677
+ if( pPg->aPg==0 ){
206678
+ pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES);
206679
+ if( pPg->aPg==0 ){
206680
+ return SQLITE_NOMEM_BKPT;
206681
+ }
206682
+ memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES);
206683
+ }
206684
+
206685
+ rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0);
206686
+ if( rc==SQLITE_OK ){
206687
+ const u8 *a = sqlite3PagerGetData(pDbPage);
206688
+ memcpy(pPg->aPg, a, pgsz);
206689
+ sqlite3PagerUnref(pDbPage);
206690
+ }
206691
+
206692
+ return rc;
206693
+}
206543206694
206544206695
/*
206545206696
** Move a DBSTAT cursor to the next entry. Normally, the next
206546206697
** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0),
206547206698
** the next entry is the next btree.
@@ -206557,11 +206708,11 @@
206557206708
206558206709
sqlite3_free(pCsr->zPath);
206559206710
pCsr->zPath = 0;
206560206711
206561206712
statNextRestart:
206562
- if( pCsr->aPage[0].pPg==0 ){
206713
+ if( pCsr->iPage<0 ){
206563206714
/* Start measuring space on the next btree */
206564206715
statResetCounts(pCsr);
206565206716
rc = sqlite3_step(pCsr->pStmt);
206566206717
if( rc==SQLITE_ROW ){
206567206718
int nPage;
@@ -206569,11 +206720,11 @@
206569206720
sqlite3PagerPagecount(pPager, &nPage);
206570206721
if( nPage==0 ){
206571206722
pCsr->isEof = 1;
206572206723
return sqlite3_reset(pCsr->pStmt);
206573206724
}
206574
- rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0);
206725
+ rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]);
206575206726
pCsr->aPage[0].iPgno = iRoot;
206576206727
pCsr->aPage[0].iCell = 0;
206577206728
if( !pCsr->isAgg ){
206578206729
pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
206579206730
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206620,13 +206771,12 @@
206620206771
p->iCell++;
206621206772
}
206622206773
206623206774
if( !p->iRightChildPg || p->iCell>p->nCell ){
206624206775
statClearPage(p);
206625
- if( pCsr->iPage>0 ){
206626
- pCsr->iPage--;
206627
- }else if( pCsr->isAgg ){
206776
+ pCsr->iPage--;
206777
+ if( pCsr->isAgg && pCsr->iPage<0 ){
206628206778
/* label-statNext-done: When computing aggregate space usage over
206629206779
** an entire btree, this is the exit point from this function */
206630206780
return SQLITE_OK;
206631206781
}
206632206782
goto statNextRestart; /* Tail recursion */
@@ -206641,11 +206791,11 @@
206641206791
if( p->iCell==p->nCell ){
206642206792
p[1].iPgno = p->iRightChildPg;
206643206793
}else{
206644206794
p[1].iPgno = p->aCell[p->iCell].iChildPg;
206645206795
}
206646
- rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0);
206796
+ rc = statGetPage(pBt, p[1].iPgno, &p[1]);
206647206797
pCsr->nPage++;
206648206798
p[1].iCell = 0;
206649206799
if( !pCsr->isAgg ){
206650206800
p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
206651206801
if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206771,10 +206921,11 @@
206771206921
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
206772206922
sqlite3_free(zSql);
206773206923
}
206774206924
206775206925
if( rc==SQLITE_OK ){
206926
+ pCsr->iPage = -1;
206776206927
rc = statNext(pCursor);
206777206928
}
206778206929
return rc;
206779206930
}
206780206931
@@ -222410,10 +222561,11 @@
222410222561
}
222411222562
222412222563
assert( (pRet==0)==(p->rc!=SQLITE_OK) );
222413222564
return pRet;
222414222565
}
222566
+
222415222567
222416222568
/*
222417222569
** Release a reference to data record returned by an earlier call to
222418222570
** fts5DataRead().
222419222571
*/
@@ -223869,11 +224021,11 @@
223869224021
int pgnoLast = 0;
223870224022
223871224023
if( pDlidx ){
223872224024
int iSegid = pIter->pSeg->iSegid;
223873224025
pgnoLast = fts5DlidxIterPgno(pDlidx);
223874
- pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
224026
+ pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
223875224027
}else{
223876224028
Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
223877224029
223878224030
/* Currently, Fts5SegIter.iLeafOffset points to the first byte of
223879224031
** position-list content for the current rowid. Back it up so that it
@@ -223896,11 +224048,11 @@
223896224048
223897224049
/* The last rowid in the doclist may not be on the current page. Search
223898224050
** forward to find the page containing the last rowid. */
223899224051
for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
223900224052
i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
223901
- Fts5Data *pNew = fts5DataRead(p, iAbs);
224053
+ Fts5Data *pNew = fts5LeafRead(p, iAbs);
223902224054
if( pNew ){
223903224055
int iRowid, bTermless;
223904224056
iRowid = fts5LeafFirstRowidOff(pNew);
223905224057
bTermless = fts5LeafIsTermless(pNew);
223906224058
if( iRowid ){
@@ -223927,19 +224079,22 @@
223927224079
int iOff;
223928224080
fts5DataRelease(pIter->pLeaf);
223929224081
pIter->pLeaf = pLast;
223930224082
pIter->iLeafPgno = pgnoLast;
223931224083
iOff = fts5LeafFirstRowidOff(pLast);
224084
+ if( iOff>pLast->szLeaf ){
224085
+ p->rc = FTS5_CORRUPT;
224086
+ return;
224087
+ }
223932224088
iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
223933224089
pIter->iLeafOffset = iOff;
223934224090
223935224091
if( fts5LeafIsTermless(pLast) ){
223936224092
pIter->iEndofDoclist = pLast->nn+1;
223937224093
}else{
223938224094
pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
223939224095
}
223940
-
223941224096
}
223942224097
223943224098
fts5SegIterReverseInitPage(p, pIter);
223944224099
}
223945224100
@@ -231295,11 +231450,11 @@
231295231450
int nArg, /* Number of args */
231296231451
sqlite3_value **apUnused /* Function arguments */
231297231452
){
231298231453
assert( nArg==0 );
231299231454
UNUSED_PARAM2(nArg, apUnused);
231300
- sqlite3_result_text(pCtx, "fts5: 2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88", -1, SQLITE_TRANSIENT);
231455
+ sqlite3_result_text(pCtx, "fts5: 2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813", -1, SQLITE_TRANSIENT);
231301231456
}
231302231457
231303231458
/*
231304231459
** Return true if zName is the extension on one of the shadow tables used
231305231460
** by this module.
231306231461
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -452,11 +452,11 @@
452 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
453 ** [sqlite_version()] and [sqlite_source_id()].
454 */
455 #define SQLITE_VERSION "3.37.0"
456 #define SQLITE_VERSION_NUMBER 3037000
457 #define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
458
459 /*
460 ** CAPI3REF: Run-Time Library Version Numbers
461 ** KEYWORDS: sqlite3_version sqlite3_sourceid
462 **
@@ -13303,10 +13303,17 @@
13303 */
13304 #ifdef SQLITE_OMIT_EXPLAIN
13305 # undef SQLITE_ENABLE_EXPLAIN_COMMENTS
13306 #endif
13307
 
 
 
 
 
 
 
13308 /*
13309 ** Return true (non-zero) if the input is an integer that is too large
13310 ** to fit in 32-bits. This macro is used inside of various testcase()
13311 ** macros to verify that we have tested SQLite for large-file support.
13312 */
@@ -16403,11 +16410,11 @@
16403 u8 iDb; /* Which db file is being initialized */
16404 u8 busy; /* TRUE if currently initializing */
16405 unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
16406 unsigned imposterTable : 1; /* Building an imposter table */
16407 unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
16408 char **azInit; /* "type", "name", and "tbl_name" columns */
16409 } init;
16410 int nVdbeActive; /* Number of VDBEs currently running */
16411 int nVdbeRead; /* Number of active VDBEs that read or write */
16412 int nVdbeWrite; /* Number of active VDBEs that read and write */
16413 int nVdbeExec; /* Number of nested calls to VdbeExec() */
@@ -18967,11 +18974,11 @@
18967 SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window*);
18968 SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p);
18969 SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8);
18970 SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*);
18971 SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin);
18972 SQLITE_PRIVATE int sqlite3WindowCompare(Parse*, Window*, Window*, int);
18973 SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*);
18974 SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int);
18975 SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*);
18976 SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*);
18977 SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p);
@@ -19099,12 +19106,12 @@
19099 SQLITE_PRIVATE void *sqlite3Realloc(void*, u64);
19100 SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
19101 SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64);
19102 SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*);
19103 SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*);
19104 SQLITE_PRIVATE int sqlite3MallocSize(void*);
19105 SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*);
19106 SQLITE_PRIVATE void *sqlite3PageMalloc(int);
19107 SQLITE_PRIVATE void sqlite3PageFree(void*);
19108 SQLITE_PRIVATE void sqlite3MemSetDefault(void);
19109 #ifndef SQLITE_UNTESTABLE
19110 SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void));
@@ -19236,21 +19243,21 @@
19236 SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*);
19237 SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*);
19238 SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*);
19239 SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*);
19240 SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*);
19241 SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int);
19242 SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*);
19243 SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
19244 SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*);
19245 SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*);
19246 SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*);
19247 SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
19248 SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
19249 SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*);
19250 SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int);
19251 SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
19252 SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*);
19253 SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*);
19254 SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*);
19255 SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index*);
19256 SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**);
@@ -19429,15 +19436,15 @@
19429 SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
19430 SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
19431 SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
19432 SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*);
19433 SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*);
19434 SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*);
19435 SQLITE_PRIVATE int sqlite3ExprCompare(Parse*,Expr*, Expr*, int);
19436 SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int);
19437 SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int);
19438 SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int);
19439 SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int);
19440 SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*);
19441 SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
19442 SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
19443 SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
@@ -19464,11 +19471,11 @@
19464 SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
19465 SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
19466 #ifdef SQLITE_ENABLE_CURSOR_HINTS
19467 SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
19468 #endif
19469 SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*);
19470 SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
19471 SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
19472 SQLITE_PRIVATE int sqlite3IsRowid(const char*);
19473 SQLITE_PRIVATE void sqlite3GenerateRowDelete(
19474 Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
@@ -19489,15 +19496,15 @@
19489 SQLITE_PRIVATE void sqlite3MultiWrite(Parse*);
19490 SQLITE_PRIVATE void sqlite3MayAbort(Parse*);
19491 SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8);
19492 SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*);
19493 SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*);
19494 SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int);
19495 SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int);
19496 SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int);
19497 SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*);
19498 SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int);
19499 SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*);
19500 SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int);
19501 SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8);
19502 SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void);
19503 SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void);
@@ -19631,11 +19638,11 @@
19631
19632 SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
19633 SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
19634 SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
19635 SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
19636 SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
19637 SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr);
19638 SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
19639 SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
19640 SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
19641 SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
@@ -19660,12 +19667,12 @@
19660 SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
19661 SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8);
19662 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr);
19663 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr);
19664 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*);
19665 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
19666 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
19667 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
19668 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
19669 SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
19670 SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*);
19671 SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*);
@@ -19692,11 +19699,11 @@
19692 #endif
19693 SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *);
19694 #ifndef SQLITE_OMIT_UTF16
19695 SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8);
19696 #endif
19697 SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **);
19698 SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
19699 #ifndef SQLITE_AMALGAMATION
19700 SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
19701 SQLITE_PRIVATE const char sqlite3StrBINARY[];
19702 SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[];
@@ -19744,13 +19751,13 @@
19744 SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
19745 SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
19746 SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
19747 SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
19748 SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
19749 SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, Token*);
19750 SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse*, void*, Token*);
19751 SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom);
19752 SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*);
19753 SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*);
19754 SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
19755 SQLITE_PRIVATE char sqlite3AffinityType(const char*, Column*);
19756 SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*);
@@ -19790,10 +19797,12 @@
19790 SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
19791 SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
19792
19793 SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
19794 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
 
 
19795 SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
19796 SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
19797
19798 SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *);
19799 SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
@@ -20021,11 +20030,11 @@
20021 SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p);
20022 SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *);
20023
20024 SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p);
20025 #if SQLITE_MAX_EXPR_DEPTH>0
20026 SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *);
20027 SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int);
20028 #else
20029 #define sqlite3SelectExprHeight(x) 0
20030 #define sqlite3ExprCheckHeight(x,y)
20031 #endif
@@ -20118,12 +20127,12 @@
20118 #endif
20119 #if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)
20120 SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*);
20121 #endif
20122
20123 SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr);
20124 SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr);
20125 SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int);
20126 SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int);
20127 SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*);
20128
20129 #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
@@ -23548,135 +23557,104 @@
23548 sqlite3_context *context,
23549 int argc,
23550 sqlite3_value **argv
23551 ){
23552 DateTime x;
23553 u64 n;
23554 size_t i,j;
23555 char *z;
23556 sqlite3 *db;
23557 const char *zFmt;
23558 char zBuf[100];
 
 
23559 if( argc==0 ) return;
23560 zFmt = (const char*)sqlite3_value_text(argv[0]);
23561 if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
23562 db = sqlite3_context_db_handle(context);
23563 for(i=0, n=1; zFmt[i]; i++, n++){
23564 if( zFmt[i]=='%' ){
23565 switch( zFmt[i+1] ){
23566 case 'd':
23567 case 'H':
23568 case 'm':
23569 case 'M':
23570 case 'S':
23571 case 'W':
23572 n++;
23573 /* fall thru */
23574 case 'w':
23575 case '%':
23576 break;
23577 case 'f':
23578 n += 8;
23579 break;
23580 case 'j':
23581 n += 3;
23582 break;
23583 case 'Y':
23584 n += 8;
23585 break;
23586 case 's':
23587 case 'J':
23588 n += 50;
23589 break;
23590 default:
23591 return; /* ERROR. return a NULL */
23592 }
23593 i++;
23594 }
23595 }
23596 testcase( n==sizeof(zBuf)-1 );
23597 testcase( n==sizeof(zBuf) );
23598 testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 );
23599 testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH] );
23600 if( n<sizeof(zBuf) ){
23601 z = zBuf;
23602 }else if( n>(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){
23603 sqlite3_result_error_toobig(context);
23604 return;
23605 }else{
23606 z = sqlite3DbMallocRawNN(db, (int)n);
23607 if( z==0 ){
23608 sqlite3_result_error_nomem(context);
23609 return;
23610 }
23611 }
23612 computeJD(&x);
23613 computeYMD_HMS(&x);
23614 for(i=j=0; zFmt[i]; i++){
23615 if( zFmt[i]!='%' ){
23616 z[j++] = zFmt[i];
23617 }else{
23618 i++;
23619 switch( zFmt[i] ){
23620 case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break;
23621 case 'f': {
23622 double s = x.s;
23623 if( s>59.999 ) s = 59.999;
23624 sqlite3_snprintf(7, &z[j],"%06.3f", s);
23625 j += sqlite3Strlen30(&z[j]);
23626 break;
23627 }
23628 case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break;
23629 case 'W': /* Fall thru */
23630 case 'j': {
23631 int nDay; /* Number of days since 1st day of year */
23632 DateTime y = x;
23633 y.validJD = 0;
23634 y.M = 1;
23635 y.D = 1;
23636 computeJD(&y);
23637 nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
23638 if( zFmt[i]=='W' ){
23639 int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
23640 wd = (int)(((x.iJD+43200000)/86400000)%7);
23641 sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
23642 j += 2;
23643 }else{
23644 sqlite3_snprintf(4, &z[j],"%03d",nDay+1);
23645 j += 3;
23646 }
23647 break;
23648 }
23649 case 'J': {
23650 sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0);
23651 j+=sqlite3Strlen30(&z[j]);
23652 break;
23653 }
23654 case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break;
23655 case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
23656 case 's': {
23657 i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000);
23658 sqlite3Int64ToText(iS, &z[j]);
23659 j += sqlite3Strlen30(&z[j]);
23660 break;
23661 }
23662 case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
23663 case 'w': {
23664 z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0';
23665 break;
23666 }
23667 case 'Y': {
23668 sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]);
23669 break;
23670 }
23671 default: z[j++] = '%'; break;
23672 }
23673 }
23674 }
23675 z[j] = 0;
23676 sqlite3_result_text(context, z, -1,
23677 z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23678 }
23679
23680 /*
23681 ** current_time()
23682 **
@@ -23953,10 +23931,11 @@
23953 SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){
23954 int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize;
23955 return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
23956 }
23957 SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){
 
23958 return id->pMethods->xDeviceCharacteristics(id);
23959 }
23960 #ifndef SQLITE_OMIT_WAL
23961 SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int offset, int n, int flags){
23962 return id->pMethods->xShmLock(id, offset, n, flags);
@@ -28270,11 +28249,11 @@
28270
28271 /*
28272 ** TRUE if p is a lookaside memory allocation from db
28273 */
28274 #ifndef SQLITE_OMIT_LOOKASIDE
28275 static int isLookaside(sqlite3 *db, void *p){
28276 return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd);
28277 }
28278 #else
28279 #define isLookaside(A,B) 0
28280 #endif
@@ -28281,22 +28260,22 @@
28281
28282 /*
28283 ** Return the size of a memory allocation previously obtained from
28284 ** sqlite3Malloc() or sqlite3_malloc().
28285 */
28286 SQLITE_PRIVATE int sqlite3MallocSize(void *p){
28287 assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
28288 return sqlite3GlobalConfig.m.xSize(p);
28289 }
28290 static int lookasideMallocSize(sqlite3 *db, void *p){
28291 #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
28292 return p<db->lookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL;
28293 #else
28294 return db->lookaside.szTrue;
28295 #endif
28296 }
28297 SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){
28298 assert( p!=0 );
28299 #ifdef SQLITE_DEBUG
28300 if( db==0 || !isLookaside(db,p) ){
28301 if( db==0 ){
28302 assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
@@ -28319,11 +28298,11 @@
28319 assert( sqlite3_mutex_held(db->mutex) );
28320 return db->lookaside.szTrue;
28321 }
28322 }
28323 }
28324 return sqlite3GlobalConfig.m.xSize(p);
28325 }
28326 SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){
28327 assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
28328 assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
28329 return p ? sqlite3GlobalConfig.m.xSize(p) : 0;
@@ -28929,11 +28908,11 @@
28929 #endif /* SQLITE_OMIT_FLOATING_POINT */
28930
28931 /*
28932 ** Set the StrAccum object to an error mode.
28933 */
28934 static void setStrAccumError(StrAccum *p, u8 eError){
28935 assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
28936 p->accError = eError;
28937 if( p->mxAlloc ) sqlite3_str_reset(p);
28938 if( eError==SQLITE_TOOBIG ) sqlite3ErrorToParser(p->db, eError);
28939 }
@@ -28965,16 +28944,16 @@
28965 */
28966 static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){
28967 char *z;
28968 if( pAccum->accError ) return 0;
28969 if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
28970 setStrAccumError(pAccum, SQLITE_TOOBIG);
28971 return 0;
28972 }
28973 z = sqlite3DbMallocRaw(pAccum->db, n);
28974 if( z==0 ){
28975 setStrAccumError(pAccum, SQLITE_NOMEM);
28976 }
28977 return z;
28978 }
28979
28980 /*
@@ -29709,11 +29688,11 @@
29709 testcase(p->accError==SQLITE_TOOBIG);
29710 testcase(p->accError==SQLITE_NOMEM);
29711 return 0;
29712 }
29713 if( p->mxAlloc==0 ){
29714 setStrAccumError(p, SQLITE_TOOBIG);
29715 return p->nAlloc - p->nChar - 1;
29716 }else{
29717 char *zOld = isMalloced(p) ? p->zText : 0;
29718 i64 szNew = p->nChar;
29719 szNew += (sqlite3_int64)N + 1;
@@ -29722,11 +29701,11 @@
29722 ** to avoid having to call this routine too often */
29723 szNew += p->nChar;
29724 }
29725 if( szNew > p->mxAlloc ){
29726 sqlite3_str_reset(p);
29727 setStrAccumError(p, SQLITE_TOOBIG);
29728 return 0;
29729 }else{
29730 p->nAlloc = (int)szNew;
29731 }
29732 if( p->db ){
@@ -29740,11 +29719,11 @@
29740 p->zText = zNew;
29741 p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
29742 p->printfFlags |= SQLITE_PRINTF_MALLOCED;
29743 }else{
29744 sqlite3_str_reset(p);
29745 setStrAccumError(p, SQLITE_NOMEM);
29746 return 0;
29747 }
29748 }
29749 return N;
29750 }
@@ -29813,11 +29792,11 @@
29813 zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
29814 if( zText ){
29815 memcpy(zText, p->zText, p->nChar+1);
29816 p->printfFlags |= SQLITE_PRINTF_MALLOCED;
29817 }else{
29818 setStrAccumError(p, SQLITE_NOMEM);
29819 }
29820 p->zText = zText;
29821 return zText;
29822 }
29823 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
@@ -29827,10 +29806,26 @@
29827 return strAccumFinishRealloc(p);
29828 }
29829 }
29830 return p->zText;
29831 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29832
29833 /*
29834 ** This singleton is an sqlite3_str object that is returned if
29835 ** sqlite3_malloc() fails to provide space for a real one. This
29836 ** sqlite3_str object accepts no new text and always returns
@@ -57339,10 +57334,11 @@
57339 #endif
57340 }else{
57341 pPager->zWal = 0;
57342 }
57343 #endif
 
57344
57345 if( nPathname ) sqlite3DbFree(0, zPathname);
57346 pPager->pVfs = pVfs;
57347 pPager->vfsFlags = vfsFlags;
57348
@@ -70866,13 +70862,11 @@
70866 if( rc==SQLITE_OK ){
70867 getCellInfo(pCur);
70868 if( pCur->info.nKey==intKey ){
70869 return SQLITE_OK;
70870 }
70871 }else if( rc==SQLITE_DONE ){
70872 rc = SQLITE_OK;
70873 }else{
70874 return rc;
70875 }
70876 }
70877 }
70878 }
@@ -74541,11 +74535,11 @@
74541 nIn = pSrc->pBt->usableSize - 4;
74542 }
74543 }
74544 }while( rc==SQLITE_OK && nOut>0 );
74545
74546 if( rc==SQLITE_OK && nRem>0 ){
74547 Pgno pgnoNew;
74548 MemPage *pNew = 0;
74549 rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0);
74550 put4byte(pPgnoOut, pgnoNew);
74551 if( ISAUTOVACUUM && pPageOut ){
@@ -78403,11 +78397,11 @@
78403 ** NULL and an SQLite error code returned.
78404 */
78405 #ifdef SQLITE_ENABLE_STAT4
78406 static int valueFromFunction(
78407 sqlite3 *db, /* The database connection */
78408 Expr *p, /* The expression to evaluate */
78409 u8 enc, /* Encoding to use */
78410 u8 aff, /* Affinity to use */
78411 sqlite3_value **ppVal, /* Write the new value here */
78412 struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
78413 ){
@@ -78497,11 +78491,11 @@
78497 ** NULL, it is assumed that the caller will free any allocated object
78498 ** in all cases.
78499 */
78500 static int valueFromExpr(
78501 sqlite3 *db, /* The database connection */
78502 Expr *pExpr, /* The expression to evaluate */
78503 u8 enc, /* Encoding to use */
78504 u8 affinity, /* Affinity to use */
78505 sqlite3_value **ppVal, /* Write the new value here */
78506 struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
78507 ){
@@ -78652,11 +78646,11 @@
78652 ** the value by passing it to sqlite3ValueFree() later on. If the expression
78653 ** cannot be converted to a value, then *ppVal is set to NULL.
78654 */
78655 SQLITE_PRIVATE int sqlite3ValueFromExpr(
78656 sqlite3 *db, /* The database connection */
78657 Expr *pExpr, /* The expression to evaluate */
78658 u8 enc, /* Encoding to use */
78659 u8 affinity, /* Affinity to use */
78660 sqlite3_value **ppVal /* Write the new value here */
78661 ){
78662 return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
@@ -86316,15 +86310,13 @@
86316 Mem *pVar; /* Value of a host parameter */
86317 StrAccum out; /* Accumulate the output here */
86318 #ifndef SQLITE_OMIT_UTF16
86319 Mem utf8; /* Used to convert UTF16 into UTF8 for display */
86320 #endif
86321 char zBase[100]; /* Initial working space */
86322
86323 db = p->db;
86324 sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase),
86325 db->aLimit[SQLITE_LIMIT_LENGTH]);
86326 if( db->nVdbeExec>1 ){
86327 while( *zRawSql ){
86328 const char *zStart = zRawSql;
86329 while( *(zRawSql++)!='\n' && *zRawSql );
86330 sqlite3_str_append(&out, "-- ", 3);
@@ -94164,11 +94156,10 @@
94164 assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int );
94165 nArg = (int)pArgc->u.i;
94166 iQuery = (int)pQuery->u.i;
94167
94168 /* Invoke the xFilter method */
94169 res = 0;
94170 apArg = p->apArg;
94171 for(i = 0; i<nArg; i++){
94172 apArg[i] = &pArgc[i+1];
94173 }
94174 rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg);
@@ -94254,11 +94245,10 @@
94254 sqlite3_vtab *pVtab;
94255 const sqlite3_module *pModule;
94256 int res;
94257 VdbeCursor *pCur;
94258
94259 res = 0;
94260 pCur = p->apCsr[pOp->p1];
94261 assert( pCur->eCurType==CURTYPE_VTAB );
94262 if( pCur->nullRow ){
94263 break;
94264 }
@@ -100392,13 +100382,15 @@
100392 int nRef = pNC->nRef;
100393 testcase( pNC->ncFlags & NC_IsCheck );
100394 testcase( pNC->ncFlags & NC_PartIdx );
100395 testcase( pNC->ncFlags & NC_IdxExpr );
100396 testcase( pNC->ncFlags & NC_GenCol );
100397 sqlite3ResolveNotValid(pParse, pNC, "subqueries",
100398 NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr);
100399 sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
 
 
100400 assert( pNC->nRef>=nRef );
100401 if( nRef!=pNC->nRef ){
100402 ExprSetProperty(pExpr, EP_VarSelect);
100403 pNC->ncFlags |= NC_VarSelect;
100404 }
@@ -101322,11 +101314,11 @@
101322 static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree);
101323
101324 /*
101325 ** Return the affinity character for a single column of a table.
101326 */
101327 SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){
101328 assert( iCol<pTab->nCol );
101329 return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER;
101330 }
101331
101332 /*
@@ -101393,11 +101385,11 @@
101393 **
101394 ** If a memory allocation error occurs, that fact is recorded in pParse->db
101395 ** and the pExpr parameter is returned unchanged.
101396 */
101397 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(
101398 Parse *pParse, /* Parsing context */
101399 Expr *pExpr, /* Add the "COLLATE" clause to this expression */
101400 const Token *pCollName, /* Name of collating sequence */
101401 int dequote /* True to dequote pCollName */
101402 ){
101403 if( pCollName->n>0 ){
@@ -101408,11 +101400,15 @@
101408 pExpr = pNew;
101409 }
101410 }
101411 return pExpr;
101412 }
101413 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){
 
 
 
 
101414 Token s;
101415 assert( zC!=0 );
101416 sqlite3TokenInit(&s, (char*)zC);
101417 return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0);
101418 }
@@ -101710,21 +101706,21 @@
101710 ** columns of result. Every TK_VECTOR node is an vector because the
101711 ** parser will not generate a TK_VECTOR with fewer than two entries.
101712 ** But a TK_SELECT might be either a vector or a scalar. It is only
101713 ** considered a vector if it has two or more result columns.
101714 */
101715 SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){
101716 return sqlite3ExprVectorSize(pExpr)>1;
101717 }
101718
101719 /*
101720 ** If the expression passed as the only argument is of type TK_VECTOR
101721 ** return the number of expressions in the vector. Or, if the expression
101722 ** is a sub-select, return the number of columns in the sub-select. For
101723 ** any other type of expression, return 1.
101724 */
101725 SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
101726 u8 op = pExpr->op;
101727 if( op==TK_REGISTER ) op = pExpr->op2;
101728 if( op==TK_VECTOR ){
101729 return pExpr->x.pList->nExpr;
101730 }else if( op==TK_SELECT ){
@@ -101813,13 +101809,20 @@
101813 pRet->iTable = nField;
101814 pRet->iColumn = iField;
101815 pRet->pLeft = pVector;
101816 }
101817 }else{
101818 if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr;
 
 
 
 
 
 
 
 
101819 pRet = sqlite3ExprDup(pParse->db, pVector, 0);
101820 sqlite3RenameTokenRemap(pParse, pRet, pVector);
101821 }
101822 return pRet;
101823 }
101824
101825 /*
@@ -102008,27 +102011,27 @@
102008 **
102009 ** If this maximum height is greater than the current value pointed
102010 ** to by pnHeight, the second parameter, then set *pnHeight to that
102011 ** value.
102012 */
102013 static void heightOfExpr(Expr *p, int *pnHeight){
102014 if( p ){
102015 if( p->nHeight>*pnHeight ){
102016 *pnHeight = p->nHeight;
102017 }
102018 }
102019 }
102020 static void heightOfExprList(ExprList *p, int *pnHeight){
102021 if( p ){
102022 int i;
102023 for(i=0; i<p->nExpr; i++){
102024 heightOfExpr(p->a[i].pExpr, pnHeight);
102025 }
102026 }
102027 }
102028 static void heightOfSelect(Select *pSelect, int *pnHeight){
102029 Select *p;
102030 for(p=pSelect; p; p=p->pPrior){
102031 heightOfExpr(p->pWhere, pnHeight);
102032 heightOfExpr(p->pHaving, pnHeight);
102033 heightOfExpr(p->pLimit, pnHeight);
102034 heightOfExprList(p->pEList, pnHeight);
@@ -102076,11 +102079,11 @@
102076
102077 /*
102078 ** Return the maximum height of any expression tree referenced
102079 ** by the select statement passed as an argument.
102080 */
102081 SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
102082 int nHeight = 0;
102083 heightOfSelect(p, &nHeight);
102084 return nHeight;
102085 }
102086 #else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */
@@ -102329,11 +102332,11 @@
102329 ** arguments.
102330 */
102331 SQLITE_PRIVATE Expr *sqlite3ExprFunction(
102332 Parse *pParse, /* Parsing context */
102333 ExprList *pList, /* Argument list */
102334 Token *pToken, /* Name of the function */
102335 int eDistinct /* SF_Distinct or SF_ALL or 0 */
102336 ){
102337 Expr *pNew;
102338 sqlite3 *db = pParse->db;
102339 assert( pToken );
@@ -102367,12 +102370,12 @@
102367 **
102368 ** If the function is not usable, create an error.
102369 */
102370 SQLITE_PRIVATE void sqlite3ExprFunctionUsable(
102371 Parse *pParse, /* Parsing and code generating context */
102372 Expr *pExpr, /* The function invocation */
102373 FuncDef *pDef /* The function being invoked */
102374 ){
102375 assert( !IN_RENAME_OBJECT );
102376 assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 );
102377 if( ExprHasProperty(pExpr, EP_FromDDL) ){
102378 if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0
@@ -102548,11 +102551,11 @@
102548 /*
102549 ** Return the number of bytes allocated for the expression structure
102550 ** passed as the first argument. This is always one of EXPR_FULLSIZE,
102551 ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE.
102552 */
102553 static int exprStructSize(Expr *p){
102554 if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE;
102555 if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE;
102556 return EXPR_FULLSIZE;
102557 }
102558
@@ -102588,11 +102591,11 @@
102588 ** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal
102589 ** to reduce a pristine expression tree from the parser. The implementation
102590 ** of dupedExprStructSize() contain multiple assert() statements that attempt
102591 ** to enforce this constraint.
102592 */
102593 static int dupedExprStructSize(Expr *p, int flags){
102594 int nSize;
102595 assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */
102596 assert( EXPR_FULLSIZE<=0xfff );
102597 assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 );
102598 if( 0==flags || p->op==TK_SELECT_COLUMN
@@ -102619,11 +102622,11 @@
102619 /*
102620 ** This function returns the space in bytes required to store the copy
102621 ** of the Expr structure and a copy of the Expr.u.zToken string (if that
102622 ** string is defined.)
102623 */
102624 static int dupedExprNodeSize(Expr *p, int flags){
102625 int nByte = dupedExprStructSize(p, flags) & 0xfff;
102626 if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
102627 nByte += sqlite3Strlen30NN(p->u.zToken)+1;
102628 }
102629 return ROUND8(nByte);
@@ -102640,11 +102643,11 @@
102640 ** If the EXPRDUP_REDUCE flag is set, then the return value includes
102641 ** space to duplicate all Expr nodes in the tree formed by Expr.pLeft
102642 ** and Expr.pRight variables (but not for any structures pointed to or
102643 ** descended from the Expr.x.pList or Expr.x.pSelect variables).
102644 */
102645 static int dupedExprSize(Expr *p, int flags){
102646 int nByte = 0;
102647 if( p ){
102648 nByte = dupedExprNodeSize(p, flags);
102649 if( flags&EXPRDUP_REDUCE ){
102650 nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags);
@@ -102659,11 +102662,11 @@
102659 ** to store the copy of expression p, the copies of p->u.zToken
102660 ** (if applicable), and the copies of the p->pLeft and p->pRight expressions,
102661 ** if any. Before returning, *pzBuffer is set to the first byte past the
102662 ** portion of the buffer copied into by this function.
102663 */
102664 static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
102665 Expr *pNew; /* Value to return */
102666 u8 *zAlloc; /* Memory space from which to build Expr object */
102667 u32 staticFlag; /* EP_Static if space not obtained from malloc */
102668
102669 assert( db!=0 );
@@ -102840,17 +102843,18 @@
102840 ** The flags parameter contains a combination of the EXPRDUP_XXX flags.
102841 ** If the EXPRDUP_REDUCE flag is set, then the structure returned is a
102842 ** truncated version of the usual Expr structure that will be stored as
102843 ** part of the in-memory representation of the database schema.
102844 */
102845 SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){
102846 assert( flags==0 || flags==EXPRDUP_REDUCE );
102847 return p ? exprDup(db, p, flags, 0) : 0;
102848 }
102849 SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){
102850 ExprList *pNew;
102851 struct ExprList_item *pItem, *pOldItem;
 
102852 int i;
102853 Expr *pPriorSelectColOld = 0;
102854 Expr *pPriorSelectColNew = 0;
102855 assert( db!=0 );
102856 if( p==0 ) return 0;
@@ -102898,11 +102902,11 @@
102898 ** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes
102899 ** called with a NULL argument.
102900 */
102901 #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \
102902 || !defined(SQLITE_OMIT_SUBQUERY)
102903 SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
102904 SrcList *pNew;
102905 int i;
102906 int nByte;
102907 assert( db!=0 );
102908 if( p==0 ) return 0;
@@ -102910,11 +102914,11 @@
102910 pNew = sqlite3DbMallocRawNN(db, nByte );
102911 if( pNew==0 ) return 0;
102912 pNew->nSrc = pNew->nAlloc = p->nSrc;
102913 for(i=0; i<p->nSrc; i++){
102914 SrcItem *pNewItem = &pNew->a[i];
102915 SrcItem *pOldItem = &p->a[i];
102916 Table *pTab;
102917 pNewItem->pSchema = pOldItem->pSchema;
102918 pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
102919 pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102920 pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
@@ -102942,11 +102946,11 @@
102942 pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
102943 pNewItem->colUsed = pOldItem->colUsed;
102944 }
102945 return pNew;
102946 }
102947 SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){
102948 IdList *pNew;
102949 int i;
102950 assert( db!=0 );
102951 if( p==0 ) return 0;
102952 pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
@@ -102966,15 +102970,15 @@
102966 pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102967 pNewItem->idx = pOldItem->idx;
102968 }
102969 return pNew;
102970 }
102971 SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){
102972 Select *pRet = 0;
102973 Select *pNext = 0;
102974 Select **pp = &pRet;
102975 Select *p;
102976
102977 assert( db!=0 );
102978 for(p=pDup; p; p=p->pPrior){
102979 Select *pNew = sqlite3DbMallocRawNN(db, sizeof(*p) );
102980 if( pNew==0 ) break;
@@ -103211,11 +103215,11 @@
103211 ** is set.
103212 */
103213 SQLITE_PRIVATE void sqlite3ExprListSetName(
103214 Parse *pParse, /* Parsing context */
103215 ExprList *pList, /* List to which to add the span. */
103216 Token *pName, /* Name to be added */
103217 int dequote /* True to cause the name to be dequoted */
103218 ){
103219 assert( pList!=0 || pParse->db->mallocFailed!=0 );
103220 assert( pParse->eParseMode!=PARSE_MODE_UNMAP || dequote==0 );
103221 if( pList ){
@@ -103229,11 +103233,11 @@
103229 /* If dequote==0, then pName->z does not point to part of a DDL
103230 ** statement handled by the parser. And so no token need be added
103231 ** to the token-map. */
103232 sqlite3Dequote(pItem->zEName);
103233 if( IN_RENAME_OBJECT ){
103234 sqlite3RenameTokenMap(pParse, (void*)pItem->zEName, pName);
103235 }
103236 }
103237 }
103238 }
103239
@@ -103657,11 +103661,11 @@
103657 ** If the expression p codes a constant integer that is small enough
103658 ** to fit in a 32-bit integer, return 1 and put the value of the integer
103659 ** in *pValue. If the expression is not an integer or if it is too big
103660 ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
103661 */
103662 SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
103663 int rc = 0;
103664 if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
103665
103666 /* If an expression is an integer literal that fits in a signed 32-bit
103667 ** integer, then the EP_IntValue flag will have already been set */
@@ -103790,11 +103794,11 @@
103790 ** a pointer to the SELECT statement. If pX is not a SELECT statement,
103791 ** or if the SELECT statement needs to be manifested into a transient
103792 ** table, then return NULL.
103793 */
103794 #ifndef SQLITE_OMIT_SUBQUERY
103795 static Select *isCandidateForInOpt(Expr *pX){
103796 Select *p;
103797 SrcList *pSrc;
103798 ExprList *pEList;
103799 Table *pTab;
103800 int i;
@@ -104168,11 +104172,11 @@
104168 ** the affinities to be used for each column of the comparison.
104169 **
104170 ** It is the responsibility of the caller to ensure that the returned
104171 ** string is eventually freed using sqlite3DbFree().
104172 */
104173 static char *exprINAffinity(Parse *pParse, Expr *pExpr){
104174 Expr *pLeft = pExpr->pLeft;
104175 int nVal = sqlite3ExprVectorSize(pLeft);
104176 Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0;
104177 char *zRet;
104178
@@ -106087,11 +106091,11 @@
106087 assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
106088 if( pParse->pVdbe==0 ) return;
106089 inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
106090 if( inReg!=target ){
106091 u8 op;
106092 if( ExprHasProperty(pExpr,EP_Subquery) ){
106093 op = OP_Copy;
106094 }else{
106095 op = OP_SCopy;
106096 }
106097 sqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target);
@@ -106625,11 +106629,15 @@
106625 ** Additionally, if pExpr is a simple SQL value and the value is the
106626 ** same as that currently bound to variable pVar, non-zero is returned.
106627 ** Otherwise, if the values are not the same or if pExpr is not a simple
106628 ** SQL value, zero is returned.
106629 */
106630 static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){
 
 
 
 
106631 int res = 0;
106632 int iVar;
106633 sqlite3_value *pL, *pR = 0;
106634
106635 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
@@ -106677,11 +106685,16 @@
106677 ** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
106678 ** If pParse is NULL (the normal case) then any TK_VARIABLE term in
106679 ** Argument pParse should normally be NULL. If it is not NULL and pA or
106680 ** pB causes a return value of 2.
106681 */
106682 SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){
 
 
 
 
 
106683 u32 combinedFlags;
106684 if( pA==0 || pB==0 ){
106685 return pB==pA ? 0 : 2;
106686 }
106687 if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
@@ -106761,11 +106774,11 @@
106761 ** a malfunction will result.
106762 **
106763 ** Two NULL pointers are considered to be the same. But a NULL pointer
106764 ** always differs from a non-NULL pointer.
106765 */
106766 SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){
106767 int i;
106768 if( pA==0 && pB==0 ) return 0;
106769 if( pA==0 || pB==0 ) return 1;
106770 if( pA->nExpr!=pB->nExpr ) return 1;
106771 for(i=0; i<pA->nExpr; i++){
@@ -106780,11 +106793,11 @@
106780
106781 /*
106782 ** Like sqlite3ExprCompare() except COLLATE operators at the top-level
106783 ** are ignored.
106784 */
106785 SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){
106786 return sqlite3ExprCompare(0,
106787 sqlite3ExprSkipCollateAndLikely(pA),
106788 sqlite3ExprSkipCollateAndLikely(pB),
106789 iTab);
106790 }
@@ -106794,13 +106807,13 @@
106794 **
106795 ** Or if seenNot is true, return non-zero if Expr p can only be
106796 ** non-NULL if pNN is not NULL
106797 */
106798 static int exprImpliesNotNull(
106799 Parse *pParse, /* Parsing context */
106800 Expr *p, /* The expression to be checked */
106801 Expr *pNN, /* The expression that is NOT NULL */
106802 int iTab, /* Table being evaluated */
106803 int seenNot /* Return true only if p can be any non-NULL value */
106804 ){
106805 assert( p );
106806 assert( pNN );
@@ -106889,11 +106902,16 @@
106889 **
106890 ** When in doubt, return false. Returning true might give a performance
106891 ** improvement. Returning false might cause a performance reduction, but
106892 ** it will always give the correct answer and is hence always safe.
106893 */
106894 SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){
 
 
 
 
 
106895 if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){
106896 return 1;
106897 }
106898 if( pE2->op==TK_OR
106899 && (sqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab)
@@ -108220,11 +108238,11 @@
108220 ** After the parse finishes, renameTokenFind() routine can be used
108221 ** to look up the actual token value that created some element in
108222 ** the parse tree.
108223 */
108224 struct RenameToken {
108225 void *p; /* Parse tree element created by token t */
108226 Token t; /* The token that created parse tree element p */
108227 RenameToken *pNext; /* Next is a list of all RenameToken objects */
108228 };
108229
108230 /*
@@ -108262,13 +108280,13 @@
108262 ** if( x==y ) ...
108263 **
108264 ** Technically, as x no longer points into a valid object or to the byte
108265 ** following a valid object, it may not be used in comparison operations.
108266 */
108267 static void renameTokenCheckAll(Parse *pParse, void *pPtr){
108268 if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){
108269 RenameToken *p;
108270 u8 i = 0;
108271 for(p=pParse->pRename; p; p=p->pNext){
108272 if( p->p ){
108273 assert( p->p!=pPtr );
108274 i += *(u8*)(p->p);
@@ -108290,11 +108308,15 @@
108290 **
108291 ** The pPtr argument is returned so that this routine can be used
108292 ** with tail recursion in tokenExpr() routine, for a small performance
108293 ** improvement.
108294 */
108295 SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){
 
 
 
 
108296 RenameToken *pNew;
108297 assert( pPtr || pParse->db->mallocFailed );
108298 renameTokenCheckAll(pParse, pPtr);
108299 if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){
108300 pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken));
@@ -108312,11 +108334,11 @@
108312 /*
108313 ** It is assumed that there is already a RenameToken object associated
108314 ** with parse tree element pFrom. This function remaps the associated token
108315 ** to parse tree element pTo.
108316 */
108317 SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){
108318 RenameToken *p;
108319 renameTokenCheckAll(pParse, pTo);
108320 for(p=pParse->pRename; p; p=p->pNext){
108321 if( p->p==pFrom ){
108322 p->p = pTo;
@@ -108328,11 +108350,12 @@
108328 /*
108329 ** Walker callback used by sqlite3RenameExprUnmap().
108330 */
108331 static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){
108332 Parse *pParse = pWalker->pParse;
108333 sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr);
 
108334 return WRC_Continue;
108335 }
108336
108337 /*
108338 ** Iterate through the Select objects that are part of WITH clauses attached
@@ -108358,10 +108381,11 @@
108358 Select *p = pWith->a[i].pSelect;
108359 NameContext sNC;
108360 memset(&sNC, 0, sizeof(sNC));
108361 sNC.pParse = pParse;
108362 if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC);
 
108363 sqlite3WalkSelect(pWalker, p);
108364 sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols);
108365 }
108366 if( pCopy && pParse->pWith==pCopy ){
108367 pParse->pWith = pCopy->pOuter;
@@ -108372,16 +108396,16 @@
108372 /*
108373 ** Unmap all tokens in the IdList object passed as the second argument.
108374 */
108375 static void unmapColumnIdlistNames(
108376 Parse *pParse,
108377 IdList *pIdList
108378 ){
108379 if( pIdList ){
108380 int ii;
108381 for(ii=0; ii<pIdList->nId; ii++){
108382 sqlite3RenameTokenRemap(pParse, 0, (void*)pIdList->a[ii].zName);
108383 }
108384 }
108385 }
108386
108387 /*
@@ -108389,13 +108413,11 @@
108389 */
108390 static int renameUnmapSelectCb(Walker *pWalker, Select *p){
108391 Parse *pParse = pWalker->pParse;
108392 int i;
108393 if( pParse->nErr ) return WRC_Abort;
108394 if( p->selFlags & (SF_View|SF_CopyCte) ){
108395 testcase( p->selFlags & SF_View );
108396 testcase( p->selFlags & SF_CopyCte );
108397 return WRC_Prune;
108398 }
108399 if( ALWAYS(p->pEList) ){
108400 ExprList *pList = p->pEList;
108401 for(i=0; i<pList->nExpr; i++){
@@ -108406,11 +108428,11 @@
108406 }
108407 if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */
108408 SrcList *pSrc = p->pSrc;
108409 for(i=0; i<pSrc->nSrc; i++){
108410 sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName);
108411 if( sqlite3WalkExpr(pWalker, pSrc->a[i].pOn) ) return WRC_Abort;
108412 unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing);
108413 }
108414 }
108415
108416 renameWalkWith(pWalker, p);
@@ -108474,11 +108496,11 @@
108474 ** the list maintained by the RenameCtx object.
108475 */
108476 static RenameToken *renameTokenFind(
108477 Parse *pParse,
108478 struct RenameCtx *pCtx,
108479 void *pPtr
108480 ){
108481 RenameToken **pp;
108482 if( NEVER(pPtr==0) ){
108483 return 0;
108484 }
@@ -108593,22 +108615,22 @@
108593 ** to the RenameCtx pCtx.
108594 */
108595 static void renameColumnElistNames(
108596 Parse *pParse,
108597 RenameCtx *pCtx,
108598 ExprList *pEList,
108599 const char *zOld
108600 ){
108601 if( pEList ){
108602 int i;
108603 for(i=0; i<pEList->nExpr; i++){
108604 char *zName = pEList->a[i].zEName;
108605 if( ALWAYS(pEList->a[i].eEName==ENAME_NAME)
108606 && ALWAYS(zName!=0)
108607 && 0==sqlite3_stricmp(zName, zOld)
108608 ){
108609 renameTokenFind(pParse, pCtx, (void*)zName);
108610 }
108611 }
108612 }
108613 }
108614
@@ -108618,19 +108640,19 @@
108618 ** from Parse object pParse and add it to the RenameCtx pCtx.
108619 */
108620 static void renameColumnIdlistNames(
108621 Parse *pParse,
108622 RenameCtx *pCtx,
108623 IdList *pIdList,
108624 const char *zOld
108625 ){
108626 if( pIdList ){
108627 int i;
108628 for(i=0; i<pIdList->nId; i++){
108629 char *zName = pIdList->a[i].zName;
108630 if( 0==sqlite3_stricmp(zName, zOld) ){
108631 renameTokenFind(pParse, pCtx, (void*)zName);
108632 }
108633 }
108634 }
108635 }
108636
@@ -109326,11 +109348,11 @@
109326 return;
109327 }
109328
109329 static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){
109330 if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){
109331 renameTokenFind(pWalker->pParse, pWalker->u.pRename, (void*)pExpr);
109332 }
109333 return WRC_Continue;
109334 }
109335
109336 /*
@@ -109596,11 +109618,11 @@
109596 ** ALTER TABLE pSrc DROP COLUMN pName
109597 **
109598 ** statement. Argument pSrc contains the possibly qualified name of the
109599 ** table being edited, and token pName the name of the column to drop.
109600 */
109601 SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, Token *pName){
109602 sqlite3 *db = pParse->db; /* Database handle */
109603 Table *pTab; /* Table to modify */
109604 int iDb; /* Index of db containing pTab in aDb[] */
109605 const char *zDb; /* Database containing pTab ("main" etc.) */
109606 char *zCol = 0; /* Name of column to drop */
@@ -110181,11 +110203,10 @@
110181 n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */
110182 + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */
110183 + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample);
110184 }
110185 #endif
110186 db = sqlite3_context_db_handle(context);
110187 p = sqlite3DbMallocZero(db, n);
110188 if( p==0 ){
110189 sqlite3_result_error_nomem(context);
110190 return;
110191 }
@@ -110600,32 +110621,23 @@
110600 ** If D is the count of distinct values and K is the total number of
110601 ** rows, then each estimate is computed as:
110602 **
110603 ** I = (K+D-1)/D
110604 */
110605 char *z;
110606 int i;
110607
110608 char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 );
110609 if( zRet==0 ){
110610 sqlite3_result_error_nomem(context);
110611 return;
110612 }
110613
110614 sqlite3_snprintf(24, zRet, "%llu",
110615 p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow);
110616 z = zRet + sqlite3Strlen30(zRet);
110617 for(i=0; i<p->nKeyCol; i++){
110618 u64 nDistinct = p->current.anDLt[i] + 1;
110619 u64 iVal = (p->nRow + nDistinct - 1) / nDistinct;
110620 sqlite3_snprintf(24, z, " %llu", iVal);
110621 z += sqlite3Strlen30(z);
110622 assert( p->current.anEq[i] );
110623 }
110624 assert( z[0]=='\0' && z>zRet );
110625
110626 sqlite3_result_text(context, zRet, -1, sqlite3_free);
110627 }
110628 #ifdef SQLITE_ENABLE_STAT4
110629 else if( eCall==STAT_GET_ROWID ){
110630 if( p->iGet<0 ){
110631 samplePushPrevious(p, 0);
@@ -110640,10 +110652,12 @@
110640 SQLITE_TRANSIENT);
110641 }
110642 }
110643 }else{
110644 tRowcnt *aCnt = 0;
 
 
110645
110646 assert( p->iGet<p->nSample );
110647 switch( eCall ){
110648 case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break;
110649 case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break;
@@ -110651,27 +110665,16 @@
110651 aCnt = p->a[p->iGet].anDLt;
110652 p->iGet++;
110653 break;
110654 }
110655 }
110656
110657 {
110658 char *zRet = sqlite3MallocZero(p->nCol * 25);
110659 if( zRet==0 ){
110660 sqlite3_result_error_nomem(context);
110661 }else{
110662 int i;
110663 char *z = zRet;
110664 for(i=0; i<p->nCol; i++){
110665 sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]);
110666 z += sqlite3Strlen30(z);
110667 }
110668 assert( z[0]=='\0' && z>zRet );
110669 z[-1] = '\0';
110670 sqlite3_result_text(context, zRet, -1, sqlite3_free);
110671 }
110672 }
110673 }
110674 #endif /* SQLITE_ENABLE_STAT4 */
110675 #ifndef SQLITE_DEBUG
110676 UNUSED_PARAMETER( argc );
110677 #endif
@@ -111588,13 +111591,16 @@
111588 ** Load content from the sqlite_stat4 table into
111589 ** the Index.aSample[] arrays of all indices.
111590 */
111591 static int loadStat4(sqlite3 *db, const char *zDb){
111592 int rc = SQLITE_OK; /* Result codes from subroutines */
 
111593
111594 assert( db->lookaside.bDisable );
111595 if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){
 
 
111596 rc = loadStatTbl(db,
111597 "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx",
111598 "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4",
111599 zDb
111600 );
@@ -111627,10 +111633,11 @@
111627 analysisInfo sInfo;
111628 HashElem *i;
111629 char *zSql;
111630 int rc = SQLITE_OK;
111631 Schema *pSchema = db->aDb[iDb].pSchema;
 
111632
111633 assert( iDb>=0 && iDb<db->nDb );
111634 assert( db->aDb[iDb].pBt!=0 );
111635
111636 /* Clear any prior statistics */
@@ -111649,11 +111656,13 @@
111649 }
111650
111651 /* Load new statistics out of the sqlite_stat1 table */
111652 sInfo.db = db;
111653 sInfo.zDatabase = db->aDb[iDb].zDbSName;
111654 if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){
 
 
111655 zSql = sqlite3MPrintf(db,
111656 "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
111657 if( zSql==0 ){
111658 rc = SQLITE_NOMEM_BKPT;
111659 }else{
@@ -113453,14 +113462,14 @@
113453 **
113454 ** Tokens are often just pointers into the original SQL text and so
113455 ** are not \000 terminated and are not persistent. The returned string
113456 ** is \000 terminated and is persistent.
113457 */
113458 SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){
113459 char *zName;
113460 if( pName ){
113461 zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n);
113462 sqlite3Dequote(zName);
113463 }else{
113464 zName = 0;
113465 }
113466 return zName;
@@ -116947,11 +116956,11 @@
116947 if( pTab ){
116948 /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list.
116949 ** The list was already ordered when this routine was entered, so at this
116950 ** point at most a single index (the newly added index) will be out of
116951 ** order. So we have to reorder at most one index. */
116952 Index **ppFrom = &pTab->pIndex;
116953 Index *pThis;
116954 for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){
116955 Index *pNext;
116956 if( pThis->onError!=OE_Replace ) continue;
116957 while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){
@@ -121358,101 +121367,167 @@
121358 minMaxValueFinalize(context, 0);
121359 }
121360
121361 /*
121362 ** group_concat(EXPR, ?SEPARATOR?)
 
 
 
 
 
 
121363 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121364 static void groupConcatStep(
121365 sqlite3_context *context,
121366 int argc,
121367 sqlite3_value **argv
121368 ){
121369 const char *zVal;
121370 StrAccum *pAccum;
121371 const char *zSep;
121372 int nVal, nSep;
121373 assert( argc==1 || argc==2 );
121374 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121375 pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
121376
121377 if( pAccum ){
121378 sqlite3 *db = sqlite3_context_db_handle(context);
121379 int firstTerm = pAccum->mxAlloc==0;
121380 pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
121381 if( !firstTerm ){
121382 if( argc==2 ){
121383 zSep = (char*)sqlite3_value_text(argv[1]);
121384 nSep = sqlite3_value_bytes(argv[1]);
121385 }else{
121386 zSep = ",";
121387 nSep = 1;
121388 }
121389 if( zSep ) sqlite3_str_append(pAccum, zSep, nSep);
121390 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121391 zVal = (char*)sqlite3_value_text(argv[0]);
121392 nVal = sqlite3_value_bytes(argv[0]);
121393 if( zVal ) sqlite3_str_append(pAccum, zVal, nVal);
121394 }
121395 }
 
121396 #ifndef SQLITE_OMIT_WINDOWFUNC
121397 static void groupConcatInverse(
121398 sqlite3_context *context,
121399 int argc,
121400 sqlite3_value **argv
121401 ){
121402 int n;
121403 StrAccum *pAccum;
121404 assert( argc==1 || argc==2 );
 
121405 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121406 pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
121407 /* pAccum is always non-NULL since groupConcatStep() will have always
121408 ** run frist to initialize it */
121409 if( ALWAYS(pAccum) ){
121410 n = sqlite3_value_bytes(argv[0]);
121411 if( argc==2 ){
121412 n += sqlite3_value_bytes(argv[1]);
121413 }else{
121414 n++;
121415 }
121416 if( n>=(int)pAccum->nChar ){
121417 pAccum->nChar = 0;
121418 }else{
121419 pAccum->nChar -= n;
121420 memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar);
121421 }
121422 if( pAccum->nChar==0 ) pAccum->mxAlloc = 0;
 
 
 
 
 
 
 
 
 
 
 
121423 }
121424 }
121425 #else
121426 # define groupConcatInverse 0
121427 #endif /* SQLITE_OMIT_WINDOWFUNC */
121428 static void groupConcatFinalize(sqlite3_context *context){
121429 StrAccum *pAccum;
121430 pAccum = sqlite3_aggregate_context(context, 0);
121431 if( pAccum ){
121432 if( pAccum->accError==SQLITE_TOOBIG ){
121433 sqlite3_result_error_toobig(context);
121434 }else if( pAccum->accError==SQLITE_NOMEM ){
121435 sqlite3_result_error_nomem(context);
121436 }else{
121437 sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1,
121438 sqlite3_free);
121439 }
121440 }
121441 }
121442 #ifndef SQLITE_OMIT_WINDOWFUNC
121443 static void groupConcatValue(sqlite3_context *context){
121444 sqlite3_str *pAccum;
121445 pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0);
121446 if( pAccum ){
 
121447 if( pAccum->accError==SQLITE_TOOBIG ){
121448 sqlite3_result_error_toobig(context);
121449 }else if( pAccum->accError==SQLITE_NOMEM ){
121450 sqlite3_result_error_nomem(context);
121451 }else{
121452 const char *zText = sqlite3_str_value(pAccum);
121453 sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
121454 }
121455 }
121456 }
121457 #else
121458 # define groupConcatValue 0
@@ -131668,11 +131743,11 @@
131668 if( sqlite3Config.bExtraSchemaChecks ){
131669 corruptSchema(pData, argv, "invalid rootpage");
131670 }
131671 }
131672 db->init.orphanTrigger = 0;
131673 db->init.azInit = argv;
131674 pStmt = 0;
131675 TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0);
131676 rc = db->errCode;
131677 assert( (rc&0xFF)==(rcp&0xFF) );
131678 db->init.iDb = saved_iDb;
@@ -131687,10 +131762,11 @@
131687 }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){
131688 corruptSchema(pData, argv, sqlite3_errmsg(db));
131689 }
131690 }
131691 }
 
131692 sqlite3_finalize(pStmt);
131693 }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){
131694 corruptSchema(pData, argv, 0);
131695 }else{
131696 /* If the SQL column is blank it means this is an index that
@@ -134990,11 +135066,11 @@
134990 SelectDest *pDest /* What to do with query results */
134991 ){
134992 SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */
134993 int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */
134994 Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */
134995 Select *pSetup = p->pPrior; /* The setup query */
134996 Select *pFirstRec; /* Left-most recursive term */
134997 int addrTop; /* Top of the loop */
134998 int addrCont, addrBreak; /* CONTINUE and BREAK addresses */
134999 int iCurrent = 0; /* The Current table */
135000 int regCurrent; /* Register holding Current table */
@@ -135074,11 +135150,10 @@
135074 ** functions. Mark the recursive elements as UNION ALL even if they
135075 ** are really UNION because the distinctness will be enforced by the
135076 ** iDistinct table. pFirstRec is left pointing to the left-most
135077 ** recursive term of the CTE.
135078 */
135079 pFirstRec = p;
135080 for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){
135081 if( pFirstRec->selFlags & SF_Aggregate ){
135082 sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
135083 goto end_of_recursive_query;
135084 }
@@ -138055,13 +138130,13 @@
138055 ){
138056 sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
138057 pTab->zName);
138058 }
138059 pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0);
138060 }else
138061 #ifndef SQLITE_OMIT_VIRTUALTABLE
138062 if( ALWAYS(IsVirtual(pTab))
138063 && pFrom->fg.fromDDL
138064 && ALWAYS(pTab->u.vtab.p!=0)
138065 && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0)
138066 ){
138067 sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"",
@@ -144520,10 +144595,11 @@
144520 VtabCtx *pCtx;
144521 int rc = SQLITE_OK;
144522 Table *pTab;
144523 char *zErr = 0;
144524 Parse sParse;
 
144525
144526 #ifdef SQLITE_ENABLE_API_ARMOR
144527 if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){
144528 return SQLITE_MISUSE_BKPT;
144529 }
@@ -144539,10 +144615,16 @@
144539 assert( IsVirtual(pTab) );
144540
144541 memset(&sParse, 0, sizeof(sParse));
144542 sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
144543 sParse.db = db;
 
 
 
 
 
 
144544 sParse.nQueryLoop = 1;
144545 if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr)
144546 && sParse.pNewTable
144547 && !db->mallocFailed
144548 && IsOrdinaryTable(sParse.pNewTable)
@@ -144585,10 +144667,11 @@
144585 if( sParse.pVdbe ){
144586 sqlite3VdbeFinalize(sParse.pVdbe);
144587 }
144588 sqlite3DeleteTable(db, sParse.pNewTable);
144589 sqlite3ParserReset(&sParse);
 
144590
144591 assert( (rc&0xff)==rc );
144592 rc = sqlite3ApiExit(db, rc);
144593 sqlite3_mutex_leave(db->mutex);
144594 return rc;
@@ -154396,10 +154479,11 @@
154396 WhereLoop *pLoop;
154397 int iCur;
154398 int j;
154399 Table *pTab;
154400 Index *pIdx;
 
154401
154402 pWInfo = pBuilder->pWInfo;
154403 if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0;
154404 assert( pWInfo->pTabList->nSrc>=1 );
154405 pItem = pWInfo->pTabList->a;
@@ -154409,13 +154493,14 @@
154409 iCur = pItem->iCursor;
154410 pWC = &pWInfo->sWC;
154411 pLoop = pBuilder->pNew;
154412 pLoop->wsFlags = 0;
154413 pLoop->nSkip = 0;
154414 pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0);
154415 if( pTerm ){
154416 testcase( pTerm->eOperator & WO_IS );
 
154417 pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
154418 pLoop->aLTerm[0] = pTerm;
154419 pLoop->nLTerm = 1;
154420 pLoop->u.btree.nEq = 1;
154421 /* TUNING: Cost of a rowid lookup is 10 */
@@ -154428,11 +154513,12 @@
154428 || pIdx->pPartIdxWhere!=0
154429 || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace)
154430 ) continue;
154431 opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ;
154432 for(j=0; j<pIdx->nKeyCol; j++){
154433 pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx);
 
154434 if( pTerm==0 ) break;
154435 testcase( pTerm->eOperator & WO_IS );
154436 pLoop->aLTerm[j] = pTerm;
154437 }
154438 if( j!=pIdx->nKeyCol ) continue;
@@ -154457,12 +154543,18 @@
154457 pWInfo->nRowOut = 1;
154458 if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr;
154459 if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){
154460 pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
154461 }
 
154462 #ifdef SQLITE_DEBUG
154463 pLoop->cId = '0';
 
 
 
 
 
154464 #endif
154465 return 1;
154466 }
154467 return 0;
154468 }
@@ -156839,11 +156931,16 @@
156839 /*
156840 ** Return 0 if the two window objects are identical, 1 if they are
156841 ** different, or 2 if it cannot be determined if the objects are identical
156842 ** or not. Identical window objects can be processed in a single scan.
156843 */
156844 SQLITE_PRIVATE int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2, int bFilter){
 
 
 
 
 
156845 int res;
156846 if( NEVER(p1==0) || NEVER(p2==0) ) return 1;
156847 if( p1->eFrmType!=p2->eFrmType ) return 1;
156848 if( p1->eStart!=p2->eStart ) return 1;
156849 if( p1->eEnd!=p2->eEnd ) return 1;
@@ -168909,10 +169006,11 @@
168909 db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
168910 db->autoCommit = 1;
168911 db->nextAutovac = -1;
168912 db->szMmap = sqlite3GlobalConfig.szMmap;
168913 db->nextPagesize = 0;
 
168914 #ifdef SQLITE_ENABLE_SORTER_MMAP
168915 /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map
168916 ** the temporary files used to do external sorts (see code in vdbesort.c)
168917 ** is disabled. It can still be used either by defining
168918 ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the
@@ -170190,13 +170288,15 @@
170190 ** passing free() a pointer that was not obtained from malloc() - it is
170191 ** an error that we cannot easily detect but that will likely cause memory
170192 ** corruption.
170193 */
170194 SQLITE_API const char *sqlite3_filename_database(const char *zFilename){
 
170195 return databaseName(zFilename);
170196 }
170197 SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){
 
170198 zFilename = databaseName(zFilename);
170199 zFilename += sqlite3Strlen30(zFilename) + 1;
170200 while( zFilename[0] ){
170201 zFilename += sqlite3Strlen30(zFilename) + 1;
170202 zFilename += sqlite3Strlen30(zFilename) + 1;
@@ -170206,11 +170306,11 @@
170206 SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){
170207 #ifdef SQLITE_OMIT_WAL
170208 return 0;
170209 #else
170210 zFilename = sqlite3_filename_journal(zFilename);
170211 zFilename += sqlite3Strlen30(zFilename) + 1;
170212 return zFilename;
170213 #endif
170214 }
170215
170216 /*
@@ -190887,11 +190987,11 @@
190887 }
190888 if( pNode->u.zJContent[0]=='-' ){ i = -i; }
190889 sqlite3_result_int64(pCtx, i);
190890 int_done:
190891 break;
190892 int_as_real: i=0; /* no break */ deliberate_fall_through
190893 }
190894 case JSON_REAL: {
190895 double r;
190896 #ifdef SQLITE_AMALGAMATION
190897 const char *z = pNode->u.zJContent;
@@ -195462,10 +195562,14 @@
195462 ){
195463 int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64);
195464 xSetMapping = ((iHeight==0)?rowidWrite:parentWrite);
195465 if( iHeight>0 ){
195466 RtreeNode *pChild = nodeHashLookup(pRtree, iRowid);
 
 
 
 
195467 if( pChild ){
195468 nodeRelease(pRtree, pChild->pParent);
195469 nodeReference(pNode);
195470 pChild->pParent = pNode;
195471 }
@@ -206052,10 +206156,19 @@
206052
206053 /* #include "sqliteInt.h" ** Requires access to internal data structures ** */
206054 #if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \
206055 && !defined(SQLITE_OMIT_VIRTUALTABLE)
206056
 
 
 
 
 
 
 
 
 
206057 /*
206058 ** Page paths:
206059 **
206060 ** The value of the 'path' column describes the path taken from the
206061 ** root-node of the b-tree structure to each page. The value of the
@@ -206119,13 +206232,12 @@
206119 };
206120
206121 /* Size information for a single btree page */
206122 struct StatPage {
206123 u32 iPgno; /* Page number */
206124 DbPage *pPg; /* Page content */
206125 int iCell; /* Current cell */
206126
206127 char *zPath; /* Path to this page */
206128
206129 /* Variables populated by statDecodePage(): */
206130 u8 flags; /* Copy of flags byte */
206131 int nCell; /* Number of cells on page */
@@ -206333,22 +206445,29 @@
206333 p->nCell = 0;
206334 p->aCell = 0;
206335 }
206336
206337 static void statClearPage(StatPage *p){
 
206338 statClearCells(p);
206339 sqlite3PagerUnref(p->pPg);
206340 sqlite3_free(p->zPath);
206341 memset(p, 0, sizeof(StatPage));
 
206342 }
206343
206344 static void statResetCsr(StatCursor *pCsr){
206345 int i;
206346 sqlite3_reset(pCsr->pStmt);
 
 
 
206347 for(i=0; i<ArraySize(pCsr->aPage); i++){
206348 statClearPage(&pCsr->aPage[i]);
 
 
206349 }
 
206350 pCsr->iPage = 0;
206351 sqlite3_free(pCsr->zPath);
206352 pCsr->zPath = 0;
206353 pCsr->isEof = 0;
206354 }
@@ -206409,11 +206528,11 @@
206409 int iOff;
206410 int nHdr;
206411 int isLeaf;
206412 int szPage;
206413
206414 u8 *aData = sqlite3PagerGetData(p->pPg);
206415 u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
206416
206417 p->flags = aHdr[0];
206418 if( p->flags==0x0A || p->flags==0x0D ){
206419 isLeaf = 1;
@@ -206480,11 +206599,11 @@
206480 assert( nPayload>=(u32)nLocal );
206481 assert( nLocal<=(nUsable-35) );
206482 if( nPayload>(u32)nLocal ){
206483 int j;
206484 int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
206485 if( iOff+nLocal>nUsable || nPayload>0x7fffffff ){
206486 goto statPageIsCorrupt;
206487 }
206488 pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
206489 pCell->nOvfl = nOvfl;
206490 pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
@@ -206538,10 +206657,42 @@
206538 /* Not ZIPVFS: The default page size and offset */
206539 pCsr->szPage += sqlite3BtreeGetPageSize(pBt);
206540 pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
206541 }
206542 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206543
206544 /*
206545 ** Move a DBSTAT cursor to the next entry. Normally, the next
206546 ** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0),
206547 ** the next entry is the next btree.
@@ -206557,11 +206708,11 @@
206557
206558 sqlite3_free(pCsr->zPath);
206559 pCsr->zPath = 0;
206560
206561 statNextRestart:
206562 if( pCsr->aPage[0].pPg==0 ){
206563 /* Start measuring space on the next btree */
206564 statResetCounts(pCsr);
206565 rc = sqlite3_step(pCsr->pStmt);
206566 if( rc==SQLITE_ROW ){
206567 int nPage;
@@ -206569,11 +206720,11 @@
206569 sqlite3PagerPagecount(pPager, &nPage);
206570 if( nPage==0 ){
206571 pCsr->isEof = 1;
206572 return sqlite3_reset(pCsr->pStmt);
206573 }
206574 rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0);
206575 pCsr->aPage[0].iPgno = iRoot;
206576 pCsr->aPage[0].iCell = 0;
206577 if( !pCsr->isAgg ){
206578 pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
206579 if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206620,13 +206771,12 @@
206620 p->iCell++;
206621 }
206622
206623 if( !p->iRightChildPg || p->iCell>p->nCell ){
206624 statClearPage(p);
206625 if( pCsr->iPage>0 ){
206626 pCsr->iPage--;
206627 }else if( pCsr->isAgg ){
206628 /* label-statNext-done: When computing aggregate space usage over
206629 ** an entire btree, this is the exit point from this function */
206630 return SQLITE_OK;
206631 }
206632 goto statNextRestart; /* Tail recursion */
@@ -206641,11 +206791,11 @@
206641 if( p->iCell==p->nCell ){
206642 p[1].iPgno = p->iRightChildPg;
206643 }else{
206644 p[1].iPgno = p->aCell[p->iCell].iChildPg;
206645 }
206646 rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0);
206647 pCsr->nPage++;
206648 p[1].iCell = 0;
206649 if( !pCsr->isAgg ){
206650 p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
206651 if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206771,10 +206921,11 @@
206771 rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
206772 sqlite3_free(zSql);
206773 }
206774
206775 if( rc==SQLITE_OK ){
 
206776 rc = statNext(pCursor);
206777 }
206778 return rc;
206779 }
206780
@@ -222410,10 +222561,11 @@
222410 }
222411
222412 assert( (pRet==0)==(p->rc!=SQLITE_OK) );
222413 return pRet;
222414 }
 
222415
222416 /*
222417 ** Release a reference to data record returned by an earlier call to
222418 ** fts5DataRead().
222419 */
@@ -223869,11 +224021,11 @@
223869 int pgnoLast = 0;
223870
223871 if( pDlidx ){
223872 int iSegid = pIter->pSeg->iSegid;
223873 pgnoLast = fts5DlidxIterPgno(pDlidx);
223874 pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
223875 }else{
223876 Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
223877
223878 /* Currently, Fts5SegIter.iLeafOffset points to the first byte of
223879 ** position-list content for the current rowid. Back it up so that it
@@ -223896,11 +224048,11 @@
223896
223897 /* The last rowid in the doclist may not be on the current page. Search
223898 ** forward to find the page containing the last rowid. */
223899 for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
223900 i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
223901 Fts5Data *pNew = fts5DataRead(p, iAbs);
223902 if( pNew ){
223903 int iRowid, bTermless;
223904 iRowid = fts5LeafFirstRowidOff(pNew);
223905 bTermless = fts5LeafIsTermless(pNew);
223906 if( iRowid ){
@@ -223927,19 +224079,22 @@
223927 int iOff;
223928 fts5DataRelease(pIter->pLeaf);
223929 pIter->pLeaf = pLast;
223930 pIter->iLeafPgno = pgnoLast;
223931 iOff = fts5LeafFirstRowidOff(pLast);
 
 
 
 
223932 iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
223933 pIter->iLeafOffset = iOff;
223934
223935 if( fts5LeafIsTermless(pLast) ){
223936 pIter->iEndofDoclist = pLast->nn+1;
223937 }else{
223938 pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
223939 }
223940
223941 }
223942
223943 fts5SegIterReverseInitPage(p, pIter);
223944 }
223945
@@ -231295,11 +231450,11 @@
231295 int nArg, /* Number of args */
231296 sqlite3_value **apUnused /* Function arguments */
231297 ){
231298 assert( nArg==0 );
231299 UNUSED_PARAM2(nArg, apUnused);
231300 sqlite3_result_text(pCtx, "fts5: 2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88", -1, SQLITE_TRANSIENT);
231301 }
231302
231303 /*
231304 ** Return true if zName is the extension on one of the shadow tables used
231305 ** by this module.
231306
--- src/sqlite3.c
+++ src/sqlite3.c
@@ -452,11 +452,11 @@
452 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
453 ** [sqlite_version()] and [sqlite_source_id()].
454 */
455 #define SQLITE_VERSION "3.37.0"
456 #define SQLITE_VERSION_NUMBER 3037000
457 #define SQLITE_SOURCE_ID "2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813"
458
459 /*
460 ** CAPI3REF: Run-Time Library Version Numbers
461 ** KEYWORDS: sqlite3_version sqlite3_sourceid
462 **
@@ -13303,10 +13303,17 @@
13303 */
13304 #ifdef SQLITE_OMIT_EXPLAIN
13305 # undef SQLITE_ENABLE_EXPLAIN_COMMENTS
13306 #endif
13307
13308 /*
13309 ** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE
13310 */
13311 #if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE)
13312 # define SQLITE_OMIT_ALTERTABLE
13313 #endif
13314
13315 /*
13316 ** Return true (non-zero) if the input is an integer that is too large
13317 ** to fit in 32-bits. This macro is used inside of various testcase()
13318 ** macros to verify that we have tested SQLite for large-file support.
13319 */
@@ -16403,11 +16410,11 @@
16410 u8 iDb; /* Which db file is being initialized */
16411 u8 busy; /* TRUE if currently initializing */
16412 unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
16413 unsigned imposterTable : 1; /* Building an imposter table */
16414 unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
16415 const char **azInit; /* "type", "name", and "tbl_name" columns */
16416 } init;
16417 int nVdbeActive; /* Number of VDBEs currently running */
16418 int nVdbeRead; /* Number of active VDBEs that read or write */
16419 int nVdbeWrite; /* Number of active VDBEs that read and write */
16420 int nVdbeExec; /* Number of nested calls to VdbeExec() */
@@ -18967,11 +18974,11 @@
18974 SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window*);
18975 SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p);
18976 SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8);
18977 SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*);
18978 SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin);
18979 SQLITE_PRIVATE int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int);
18980 SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*);
18981 SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int);
18982 SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*);
18983 SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*);
18984 SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p);
@@ -19099,12 +19106,12 @@
19106 SQLITE_PRIVATE void *sqlite3Realloc(void*, u64);
19107 SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
19108 SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64);
19109 SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*);
19110 SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*);
19111 SQLITE_PRIVATE int sqlite3MallocSize(const void*);
19112 SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*);
19113 SQLITE_PRIVATE void *sqlite3PageMalloc(int);
19114 SQLITE_PRIVATE void sqlite3PageFree(void*);
19115 SQLITE_PRIVATE void sqlite3MemSetDefault(void);
19116 #ifndef SQLITE_UNTESTABLE
19117 SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void));
@@ -19236,21 +19243,21 @@
19243 SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*);
19244 SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*);
19245 SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*);
19246 SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*);
19247 SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*);
19248 SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int);
19249 SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*);
19250 SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
19251 SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*);
19252 SQLITE_PRIVATE void sqlite3ExprDeferredDelete(Parse*, Expr*);
19253 SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*);
19254 SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
19255 SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
19256 SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*);
19257 SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int);
19258 SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int);
19259 SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*);
19260 SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*);
19261 SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*);
19262 SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index*);
19263 SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**);
@@ -19429,15 +19436,15 @@
19436 SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
19437 SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
19438 SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
19439 SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*);
19440 SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*);
19441 SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, const Token*);
19442 SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int);
19443 SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int);
19444 SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int);
19445 SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int);
19446 SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int);
19447 SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*);
19448 SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
19449 SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
19450 SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
@@ -19464,11 +19471,11 @@
19471 SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
19472 SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
19473 #ifdef SQLITE_ENABLE_CURSOR_HINTS
19474 SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
19475 #endif
19476 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*);
19477 SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
19478 SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
19479 SQLITE_PRIVATE int sqlite3IsRowid(const char*);
19480 SQLITE_PRIVATE void sqlite3GenerateRowDelete(
19481 Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
@@ -19489,15 +19496,15 @@
19496 SQLITE_PRIVATE void sqlite3MultiWrite(Parse*);
19497 SQLITE_PRIVATE void sqlite3MayAbort(Parse*);
19498 SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8);
19499 SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*);
19500 SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*);
19501 SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,const Expr*,int);
19502 SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int);
19503 SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int);
19504 SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,const IdList*);
19505 SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,const Select*,int);
19506 SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*);
19507 SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int);
19508 SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8);
19509 SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void);
19510 SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void);
@@ -19631,11 +19638,11 @@
19638
19639 SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
19640 SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
19641 SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2);
19642 SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity);
19643 SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table*,int);
19644 SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr);
19645 SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
19646 SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
19647 SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
19648 SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
@@ -19660,12 +19667,12 @@
19667 SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
19668 SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8);
19669 SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr);
19670 SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr);
19671 SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*);
19672 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int);
19673 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*);
19674 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
19675 SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*);
19676 SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
19677 SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*);
19678 SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*);
@@ -19692,11 +19699,11 @@
19699 #endif
19700 SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *);
19701 #ifndef SQLITE_OMIT_UTF16
19702 SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8);
19703 #endif
19704 SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **);
19705 SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
19706 #ifndef SQLITE_AMALGAMATION
19707 SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
19708 SQLITE_PRIVATE const char sqlite3StrBINARY[];
19709 SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[];
@@ -19744,13 +19751,13 @@
19751 SQLITE_PRIVATE int sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
19752 SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
19753 SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
19754 SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
19755 SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
19756 SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*);
19757 SQLITE_PRIVATE const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*);
19758 SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom);
19759 SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*);
19760 SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*);
19761 SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
19762 SQLITE_PRIVATE char sqlite3AffinityType(const char*, Column*);
19763 SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*);
@@ -19790,10 +19797,12 @@
19797 SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
19798 SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
19799
19800 SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
19801 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
19802 SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum*, u8);
19803 SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*);
19804 SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
19805 SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
19806
19807 SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *);
19808 SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
@@ -20021,11 +20030,11 @@
20030 SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p);
20031 SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *);
20032
20033 SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p);
20034 #if SQLITE_MAX_EXPR_DEPTH>0
20035 SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *);
20036 SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int);
20037 #else
20038 #define sqlite3SelectExprHeight(x) 0
20039 #define sqlite3ExprCheckHeight(x,y)
20040 #endif
@@ -20118,12 +20127,12 @@
20127 #endif
20128 #if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)
20129 SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*);
20130 #endif
20131
20132 SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr);
20133 SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr);
20134 SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int);
20135 SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int);
20136 SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*);
20137
20138 #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
@@ -23548,135 +23557,104 @@
23557 sqlite3_context *context,
23558 int argc,
23559 sqlite3_value **argv
23560 ){
23561 DateTime x;
 
23562 size_t i,j;
 
23563 sqlite3 *db;
23564 const char *zFmt;
23565 sqlite3_str sRes;
23566
23567
23568 if( argc==0 ) return;
23569 zFmt = (const char*)sqlite3_value_text(argv[0]);
23570 if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
23571 db = sqlite3_context_db_handle(context);
23572 sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
23573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23574 computeJD(&x);
23575 computeYMD_HMS(&x);
23576 for(i=j=0; zFmt[i]; i++){
23577 if( zFmt[i]!='%' ) continue;
23578 if( j<i ) sqlite3_str_append(&sRes, zFmt+j, i-j);
23579 i++;
23580 j = i + 1;
23581 switch( zFmt[i] ){
23582 case 'd': {
23583 sqlite3_str_appendf(&sRes, "%02d", x.D);
23584 break;
23585 }
23586 case 'f': {
23587 double s = x.s;
23588 if( s>59.999 ) s = 59.999;
23589 sqlite3_str_appendf(&sRes, "%06.3f", s);
23590 break;
23591 }
23592 case 'H': {
23593 sqlite3_str_appendf(&sRes, "%02d", x.h);
23594 break;
23595 }
23596 case 'W': /* Fall thru */
23597 case 'j': {
23598 int nDay; /* Number of days since 1st day of year */
23599 DateTime y = x;
23600 y.validJD = 0;
23601 y.M = 1;
23602 y.D = 1;
23603 computeJD(&y);
23604 nDay = (int)((x.iJD-y.iJD+43200000)/86400000);
23605 if( zFmt[i]=='W' ){
23606 int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
23607 wd = (int)(((x.iJD+43200000)/86400000)%7);
23608 sqlite3_str_appendf(&sRes,"%02d",(nDay+7-wd)/7);
23609 }else{
23610 sqlite3_str_appendf(&sRes,"%03d",nDay+1);
23611 }
23612 break;
23613 }
23614 case 'J': {
23615 sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0);
23616 break;
23617 }
23618 case 'm': {
23619 sqlite3_str_appendf(&sRes,"%02d",x.M);
23620 break;
23621 }
23622 case 'M': {
23623 sqlite3_str_appendf(&sRes,"%02d",x.m);
23624 break;
23625 }
23626 case 's': {
23627 i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000);
23628 sqlite3_str_appendf(&sRes,"%lld",iS);
23629 break;
23630 }
23631 case 'S': {
23632 sqlite3_str_appendf(&sRes,"%02d",(int)x.s);
23633 break;
23634 }
23635 case 'w': {
23636 sqlite3_str_appendchar(&sRes, 1,
23637 (char)(((x.iJD+129600000)/86400000) % 7) + '0');
23638 break;
23639 }
23640 case 'Y': {
23641 sqlite3_str_appendf(&sRes,"%04d",x.Y);
23642 break;
23643 }
23644 case '%': {
23645 sqlite3_str_appendchar(&sRes, 1, '%');
23646 break;
23647 }
23648 default: {
23649 sqlite3_str_reset(&sRes);
23650 return;
23651 }
23652 }
23653 }
23654 if( j<i ) sqlite3_str_append(&sRes, zFmt+j, i-j);
23655 sqlite3ResultStrAccum(context, &sRes);
23656 }
23657
23658 /*
23659 ** current_time()
23660 **
@@ -23953,10 +23931,11 @@
23931 SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){
23932 int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize;
23933 return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
23934 }
23935 SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){
23936 if( NEVER(id->pMethods==0) ) return 0;
23937 return id->pMethods->xDeviceCharacteristics(id);
23938 }
23939 #ifndef SQLITE_OMIT_WAL
23940 SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int offset, int n, int flags){
23941 return id->pMethods->xShmLock(id, offset, n, flags);
@@ -28270,11 +28249,11 @@
28249
28250 /*
28251 ** TRUE if p is a lookaside memory allocation from db
28252 */
28253 #ifndef SQLITE_OMIT_LOOKASIDE
28254 static int isLookaside(sqlite3 *db, const void *p){
28255 return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd);
28256 }
28257 #else
28258 #define isLookaside(A,B) 0
28259 #endif
@@ -28281,22 +28260,22 @@
28260
28261 /*
28262 ** Return the size of a memory allocation previously obtained from
28263 ** sqlite3Malloc() or sqlite3_malloc().
28264 */
28265 SQLITE_PRIVATE int sqlite3MallocSize(const void *p){
28266 assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
28267 return sqlite3GlobalConfig.m.xSize((void*)p);
28268 }
28269 static int lookasideMallocSize(sqlite3 *db, const void *p){
28270 #ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
28271 return p<db->lookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL;
28272 #else
28273 return db->lookaside.szTrue;
28274 #endif
28275 }
28276 SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){
28277 assert( p!=0 );
28278 #ifdef SQLITE_DEBUG
28279 if( db==0 || !isLookaside(db,p) ){
28280 if( db==0 ){
28281 assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
@@ -28319,11 +28298,11 @@
28298 assert( sqlite3_mutex_held(db->mutex) );
28299 return db->lookaside.szTrue;
28300 }
28301 }
28302 }
28303 return sqlite3GlobalConfig.m.xSize((void*)p);
28304 }
28305 SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){
28306 assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
28307 assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
28308 return p ? sqlite3GlobalConfig.m.xSize(p) : 0;
@@ -28929,11 +28908,11 @@
28908 #endif /* SQLITE_OMIT_FLOATING_POINT */
28909
28910 /*
28911 ** Set the StrAccum object to an error mode.
28912 */
28913 SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum *p, u8 eError){
28914 assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
28915 p->accError = eError;
28916 if( p->mxAlloc ) sqlite3_str_reset(p);
28917 if( eError==SQLITE_TOOBIG ) sqlite3ErrorToParser(p->db, eError);
28918 }
@@ -28965,16 +28944,16 @@
28944 */
28945 static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){
28946 char *z;
28947 if( pAccum->accError ) return 0;
28948 if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
28949 sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG);
28950 return 0;
28951 }
28952 z = sqlite3DbMallocRaw(pAccum->db, n);
28953 if( z==0 ){
28954 sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM);
28955 }
28956 return z;
28957 }
28958
28959 /*
@@ -29709,11 +29688,11 @@
29688 testcase(p->accError==SQLITE_TOOBIG);
29689 testcase(p->accError==SQLITE_NOMEM);
29690 return 0;
29691 }
29692 if( p->mxAlloc==0 ){
29693 sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
29694 return p->nAlloc - p->nChar - 1;
29695 }else{
29696 char *zOld = isMalloced(p) ? p->zText : 0;
29697 i64 szNew = p->nChar;
29698 szNew += (sqlite3_int64)N + 1;
@@ -29722,11 +29701,11 @@
29701 ** to avoid having to call this routine too often */
29702 szNew += p->nChar;
29703 }
29704 if( szNew > p->mxAlloc ){
29705 sqlite3_str_reset(p);
29706 sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
29707 return 0;
29708 }else{
29709 p->nAlloc = (int)szNew;
29710 }
29711 if( p->db ){
@@ -29740,11 +29719,11 @@
29719 p->zText = zNew;
29720 p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
29721 p->printfFlags |= SQLITE_PRINTF_MALLOCED;
29722 }else{
29723 sqlite3_str_reset(p);
29724 sqlite3StrAccumSetError(p, SQLITE_NOMEM);
29725 return 0;
29726 }
29727 }
29728 return N;
29729 }
@@ -29813,11 +29792,11 @@
29792 zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
29793 if( zText ){
29794 memcpy(zText, p->zText, p->nChar+1);
29795 p->printfFlags |= SQLITE_PRINTF_MALLOCED;
29796 }else{
29797 sqlite3StrAccumSetError(p, SQLITE_NOMEM);
29798 }
29799 p->zText = zText;
29800 return zText;
29801 }
29802 SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
@@ -29827,10 +29806,26 @@
29806 return strAccumFinishRealloc(p);
29807 }
29808 }
29809 return p->zText;
29810 }
29811
29812 /*
29813 ** Use the content of the StrAccum passed as the second argument
29814 ** as the result of an SQL function.
29815 */
29816 SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){
29817 if( p->accError ){
29818 sqlite3_result_error_code(pCtx, p->accError);
29819 sqlite3_str_reset(p);
29820 }else if( isMalloced(p) ){
29821 sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC);
29822 }else{
29823 sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
29824 sqlite3_str_reset(p);
29825 }
29826 }
29827
29828 /*
29829 ** This singleton is an sqlite3_str object that is returned if
29830 ** sqlite3_malloc() fails to provide space for a real one. This
29831 ** sqlite3_str object accepts no new text and always returns
@@ -57339,10 +57334,11 @@
57334 #endif
57335 }else{
57336 pPager->zWal = 0;
57337 }
57338 #endif
57339 (void)pPtr; /* Suppress warning about unused pPtr value */
57340
57341 if( nPathname ) sqlite3DbFree(0, zPathname);
57342 pPager->pVfs = pVfs;
57343 pPager->vfsFlags = vfsFlags;
57344
@@ -70866,13 +70862,11 @@
70862 if( rc==SQLITE_OK ){
70863 getCellInfo(pCur);
70864 if( pCur->info.nKey==intKey ){
70865 return SQLITE_OK;
70866 }
70867 }else if( rc!=SQLITE_DONE ){
 
 
70868 return rc;
70869 }
70870 }
70871 }
70872 }
@@ -74541,11 +74535,11 @@
74535 nIn = pSrc->pBt->usableSize - 4;
74536 }
74537 }
74538 }while( rc==SQLITE_OK && nOut>0 );
74539
74540 if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){
74541 Pgno pgnoNew;
74542 MemPage *pNew = 0;
74543 rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0);
74544 put4byte(pPgnoOut, pgnoNew);
74545 if( ISAUTOVACUUM && pPageOut ){
@@ -78403,11 +78397,11 @@
78397 ** NULL and an SQLite error code returned.
78398 */
78399 #ifdef SQLITE_ENABLE_STAT4
78400 static int valueFromFunction(
78401 sqlite3 *db, /* The database connection */
78402 const Expr *p, /* The expression to evaluate */
78403 u8 enc, /* Encoding to use */
78404 u8 aff, /* Affinity to use */
78405 sqlite3_value **ppVal, /* Write the new value here */
78406 struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
78407 ){
@@ -78497,11 +78491,11 @@
78491 ** NULL, it is assumed that the caller will free any allocated object
78492 ** in all cases.
78493 */
78494 static int valueFromExpr(
78495 sqlite3 *db, /* The database connection */
78496 const Expr *pExpr, /* The expression to evaluate */
78497 u8 enc, /* Encoding to use */
78498 u8 affinity, /* Affinity to use */
78499 sqlite3_value **ppVal, /* Write the new value here */
78500 struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
78501 ){
@@ -78652,11 +78646,11 @@
78646 ** the value by passing it to sqlite3ValueFree() later on. If the expression
78647 ** cannot be converted to a value, then *ppVal is set to NULL.
78648 */
78649 SQLITE_PRIVATE int sqlite3ValueFromExpr(
78650 sqlite3 *db, /* The database connection */
78651 const Expr *pExpr, /* The expression to evaluate */
78652 u8 enc, /* Encoding to use */
78653 u8 affinity, /* Affinity to use */
78654 sqlite3_value **ppVal /* Write the new value here */
78655 ){
78656 return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
@@ -86316,15 +86310,13 @@
86310 Mem *pVar; /* Value of a host parameter */
86311 StrAccum out; /* Accumulate the output here */
86312 #ifndef SQLITE_OMIT_UTF16
86313 Mem utf8; /* Used to convert UTF16 into UTF8 for display */
86314 #endif
 
86315
86316 db = p->db;
86317 sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
 
86318 if( db->nVdbeExec>1 ){
86319 while( *zRawSql ){
86320 const char *zStart = zRawSql;
86321 while( *(zRawSql++)!='\n' && *zRawSql );
86322 sqlite3_str_append(&out, "-- ", 3);
@@ -94164,11 +94156,10 @@
94156 assert( (pQuery->flags&MEM_Int)!=0 && pArgc->flags==MEM_Int );
94157 nArg = (int)pArgc->u.i;
94158 iQuery = (int)pQuery->u.i;
94159
94160 /* Invoke the xFilter method */
 
94161 apArg = p->apArg;
94162 for(i = 0; i<nArg; i++){
94163 apArg[i] = &pArgc[i+1];
94164 }
94165 rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg);
@@ -94254,11 +94245,10 @@
94245 sqlite3_vtab *pVtab;
94246 const sqlite3_module *pModule;
94247 int res;
94248 VdbeCursor *pCur;
94249
 
94250 pCur = p->apCsr[pOp->p1];
94251 assert( pCur->eCurType==CURTYPE_VTAB );
94252 if( pCur->nullRow ){
94253 break;
94254 }
@@ -100392,13 +100382,15 @@
100382 int nRef = pNC->nRef;
100383 testcase( pNC->ncFlags & NC_IsCheck );
100384 testcase( pNC->ncFlags & NC_PartIdx );
100385 testcase( pNC->ncFlags & NC_IdxExpr );
100386 testcase( pNC->ncFlags & NC_GenCol );
100387 if( pNC->ncFlags & NC_SelfRef ){
100388 notValidImpl(pParse, pNC, "subqueries", pExpr);
100389 }else{
100390 sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
100391 }
100392 assert( pNC->nRef>=nRef );
100393 if( nRef!=pNC->nRef ){
100394 ExprSetProperty(pExpr, EP_VarSelect);
100395 pNC->ncFlags |= NC_VarSelect;
100396 }
@@ -101322,11 +101314,11 @@
101314 static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree);
101315
101316 /*
101317 ** Return the affinity character for a single column of a table.
101318 */
101319 SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table *pTab, int iCol){
101320 assert( iCol<pTab->nCol );
101321 return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER;
101322 }
101323
101324 /*
@@ -101393,11 +101385,11 @@
101385 **
101386 ** If a memory allocation error occurs, that fact is recorded in pParse->db
101387 ** and the pExpr parameter is returned unchanged.
101388 */
101389 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(
101390 const Parse *pParse, /* Parsing context */
101391 Expr *pExpr, /* Add the "COLLATE" clause to this expression */
101392 const Token *pCollName, /* Name of collating sequence */
101393 int dequote /* True to dequote pCollName */
101394 ){
101395 if( pCollName->n>0 ){
@@ -101408,11 +101400,15 @@
101400 pExpr = pNew;
101401 }
101402 }
101403 return pExpr;
101404 }
101405 SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(
101406 const Parse *pParse, /* Parsing context */
101407 Expr *pExpr, /* Add the "COLLATE" clause to this expression */
101408 const char *zC /* The collating sequence name */
101409 ){
101410 Token s;
101411 assert( zC!=0 );
101412 sqlite3TokenInit(&s, (char*)zC);
101413 return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0);
101414 }
@@ -101710,21 +101706,21 @@
101706 ** columns of result. Every TK_VECTOR node is an vector because the
101707 ** parser will not generate a TK_VECTOR with fewer than two entries.
101708 ** But a TK_SELECT might be either a vector or a scalar. It is only
101709 ** considered a vector if it has two or more result columns.
101710 */
101711 SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr){
101712 return sqlite3ExprVectorSize(pExpr)>1;
101713 }
101714
101715 /*
101716 ** If the expression passed as the only argument is of type TK_VECTOR
101717 ** return the number of expressions in the vector. Or, if the expression
101718 ** is a sub-select, return the number of columns in the sub-select. For
101719 ** any other type of expression, return 1.
101720 */
101721 SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr){
101722 u8 op = pExpr->op;
101723 if( op==TK_REGISTER ) op = pExpr->op2;
101724 if( op==TK_VECTOR ){
101725 return pExpr->x.pList->nExpr;
101726 }else if( op==TK_SELECT ){
@@ -101813,13 +101809,20 @@
101809 pRet->iTable = nField;
101810 pRet->iColumn = iField;
101811 pRet->pLeft = pVector;
101812 }
101813 }else{
101814 if( pVector->op==TK_VECTOR ){
101815 Expr **ppVector = &pVector->x.pList->a[iField].pExpr;
101816 pVector = *ppVector;
101817 if( IN_RENAME_OBJECT ){
101818 /* This must be a vector UPDATE inside a trigger */
101819 *ppVector = 0;
101820 return pVector;
101821 }
101822 }
101823 pRet = sqlite3ExprDup(pParse->db, pVector, 0);
 
101824 }
101825 return pRet;
101826 }
101827
101828 /*
@@ -102008,27 +102011,27 @@
102011 **
102012 ** If this maximum height is greater than the current value pointed
102013 ** to by pnHeight, the second parameter, then set *pnHeight to that
102014 ** value.
102015 */
102016 static void heightOfExpr(const Expr *p, int *pnHeight){
102017 if( p ){
102018 if( p->nHeight>*pnHeight ){
102019 *pnHeight = p->nHeight;
102020 }
102021 }
102022 }
102023 static void heightOfExprList(const ExprList *p, int *pnHeight){
102024 if( p ){
102025 int i;
102026 for(i=0; i<p->nExpr; i++){
102027 heightOfExpr(p->a[i].pExpr, pnHeight);
102028 }
102029 }
102030 }
102031 static void heightOfSelect(const Select *pSelect, int *pnHeight){
102032 const Select *p;
102033 for(p=pSelect; p; p=p->pPrior){
102034 heightOfExpr(p->pWhere, pnHeight);
102035 heightOfExpr(p->pHaving, pnHeight);
102036 heightOfExpr(p->pLimit, pnHeight);
102037 heightOfExprList(p->pEList, pnHeight);
@@ -102076,11 +102079,11 @@
102079
102080 /*
102081 ** Return the maximum height of any expression tree referenced
102082 ** by the select statement passed as an argument.
102083 */
102084 SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *p){
102085 int nHeight = 0;
102086 heightOfSelect(p, &nHeight);
102087 return nHeight;
102088 }
102089 #else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */
@@ -102329,11 +102332,11 @@
102332 ** arguments.
102333 */
102334 SQLITE_PRIVATE Expr *sqlite3ExprFunction(
102335 Parse *pParse, /* Parsing context */
102336 ExprList *pList, /* Argument list */
102337 const Token *pToken, /* Name of the function */
102338 int eDistinct /* SF_Distinct or SF_ALL or 0 */
102339 ){
102340 Expr *pNew;
102341 sqlite3 *db = pParse->db;
102342 assert( pToken );
@@ -102367,12 +102370,12 @@
102370 **
102371 ** If the function is not usable, create an error.
102372 */
102373 SQLITE_PRIVATE void sqlite3ExprFunctionUsable(
102374 Parse *pParse, /* Parsing and code generating context */
102375 const Expr *pExpr, /* The function invocation */
102376 const FuncDef *pDef /* The function being invoked */
102377 ){
102378 assert( !IN_RENAME_OBJECT );
102379 assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 );
102380 if( ExprHasProperty(pExpr, EP_FromDDL) ){
102381 if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0
@@ -102548,11 +102551,11 @@
102551 /*
102552 ** Return the number of bytes allocated for the expression structure
102553 ** passed as the first argument. This is always one of EXPR_FULLSIZE,
102554 ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE.
102555 */
102556 static int exprStructSize(const Expr *p){
102557 if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE;
102558 if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE;
102559 return EXPR_FULLSIZE;
102560 }
102561
@@ -102588,11 +102591,11 @@
102591 ** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal
102592 ** to reduce a pristine expression tree from the parser. The implementation
102593 ** of dupedExprStructSize() contain multiple assert() statements that attempt
102594 ** to enforce this constraint.
102595 */
102596 static int dupedExprStructSize(const Expr *p, int flags){
102597 int nSize;
102598 assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */
102599 assert( EXPR_FULLSIZE<=0xfff );
102600 assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 );
102601 if( 0==flags || p->op==TK_SELECT_COLUMN
@@ -102619,11 +102622,11 @@
102622 /*
102623 ** This function returns the space in bytes required to store the copy
102624 ** of the Expr structure and a copy of the Expr.u.zToken string (if that
102625 ** string is defined.)
102626 */
102627 static int dupedExprNodeSize(const Expr *p, int flags){
102628 int nByte = dupedExprStructSize(p, flags) & 0xfff;
102629 if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
102630 nByte += sqlite3Strlen30NN(p->u.zToken)+1;
102631 }
102632 return ROUND8(nByte);
@@ -102640,11 +102643,11 @@
102643 ** If the EXPRDUP_REDUCE flag is set, then the return value includes
102644 ** space to duplicate all Expr nodes in the tree formed by Expr.pLeft
102645 ** and Expr.pRight variables (but not for any structures pointed to or
102646 ** descended from the Expr.x.pList or Expr.x.pSelect variables).
102647 */
102648 static int dupedExprSize(const Expr *p, int flags){
102649 int nByte = 0;
102650 if( p ){
102651 nByte = dupedExprNodeSize(p, flags);
102652 if( flags&EXPRDUP_REDUCE ){
102653 nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags);
@@ -102659,11 +102662,11 @@
102662 ** to store the copy of expression p, the copies of p->u.zToken
102663 ** (if applicable), and the copies of the p->pLeft and p->pRight expressions,
102664 ** if any. Before returning, *pzBuffer is set to the first byte past the
102665 ** portion of the buffer copied into by this function.
102666 */
102667 static Expr *exprDup(sqlite3 *db, const Expr *p, int dupFlags, u8 **pzBuffer){
102668 Expr *pNew; /* Value to return */
102669 u8 *zAlloc; /* Memory space from which to build Expr object */
102670 u32 staticFlag; /* EP_Static if space not obtained from malloc */
102671
102672 assert( db!=0 );
@@ -102840,17 +102843,18 @@
102843 ** The flags parameter contains a combination of the EXPRDUP_XXX flags.
102844 ** If the EXPRDUP_REDUCE flag is set, then the structure returned is a
102845 ** truncated version of the usual Expr structure that will be stored as
102846 ** part of the in-memory representation of the database schema.
102847 */
102848 SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){
102849 assert( flags==0 || flags==EXPRDUP_REDUCE );
102850 return p ? exprDup(db, p, flags, 0) : 0;
102851 }
102852 SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){
102853 ExprList *pNew;
102854 struct ExprList_item *pItem;
102855 const struct ExprList_item *pOldItem;
102856 int i;
102857 Expr *pPriorSelectColOld = 0;
102858 Expr *pPriorSelectColNew = 0;
102859 assert( db!=0 );
102860 if( p==0 ) return 0;
@@ -102898,11 +102902,11 @@
102902 ** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes
102903 ** called with a NULL argument.
102904 */
102905 #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \
102906 || !defined(SQLITE_OMIT_SUBQUERY)
102907 SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){
102908 SrcList *pNew;
102909 int i;
102910 int nByte;
102911 assert( db!=0 );
102912 if( p==0 ) return 0;
@@ -102910,11 +102914,11 @@
102914 pNew = sqlite3DbMallocRawNN(db, nByte );
102915 if( pNew==0 ) return 0;
102916 pNew->nSrc = pNew->nAlloc = p->nSrc;
102917 for(i=0; i<p->nSrc; i++){
102918 SrcItem *pNewItem = &pNew->a[i];
102919 const SrcItem *pOldItem = &p->a[i];
102920 Table *pTab;
102921 pNewItem->pSchema = pOldItem->pSchema;
102922 pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
102923 pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102924 pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
@@ -102942,11 +102946,11 @@
102946 pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
102947 pNewItem->colUsed = pOldItem->colUsed;
102948 }
102949 return pNew;
102950 }
102951 SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){
102952 IdList *pNew;
102953 int i;
102954 assert( db!=0 );
102955 if( p==0 ) return 0;
102956 pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
@@ -102966,15 +102970,15 @@
102970 pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
102971 pNewItem->idx = pOldItem->idx;
102972 }
102973 return pNew;
102974 }
102975 SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){
102976 Select *pRet = 0;
102977 Select *pNext = 0;
102978 Select **pp = &pRet;
102979 const Select *p;
102980
102981 assert( db!=0 );
102982 for(p=pDup; p; p=p->pPrior){
102983 Select *pNew = sqlite3DbMallocRawNN(db, sizeof(*p) );
102984 if( pNew==0 ) break;
@@ -103211,11 +103215,11 @@
103215 ** is set.
103216 */
103217 SQLITE_PRIVATE void sqlite3ExprListSetName(
103218 Parse *pParse, /* Parsing context */
103219 ExprList *pList, /* List to which to add the span. */
103220 const Token *pName, /* Name to be added */
103221 int dequote /* True to cause the name to be dequoted */
103222 ){
103223 assert( pList!=0 || pParse->db->mallocFailed!=0 );
103224 assert( pParse->eParseMode!=PARSE_MODE_UNMAP || dequote==0 );
103225 if( pList ){
@@ -103229,11 +103233,11 @@
103233 /* If dequote==0, then pName->z does not point to part of a DDL
103234 ** statement handled by the parser. And so no token need be added
103235 ** to the token-map. */
103236 sqlite3Dequote(pItem->zEName);
103237 if( IN_RENAME_OBJECT ){
103238 sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName);
103239 }
103240 }
103241 }
103242 }
103243
@@ -103657,11 +103661,11 @@
103661 ** If the expression p codes a constant integer that is small enough
103662 ** to fit in a 32-bit integer, return 1 and put the value of the integer
103663 ** in *pValue. If the expression is not an integer or if it is too big
103664 ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
103665 */
103666 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue){
103667 int rc = 0;
103668 if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
103669
103670 /* If an expression is an integer literal that fits in a signed 32-bit
103671 ** integer, then the EP_IntValue flag will have already been set */
@@ -103790,11 +103794,11 @@
103794 ** a pointer to the SELECT statement. If pX is not a SELECT statement,
103795 ** or if the SELECT statement needs to be manifested into a transient
103796 ** table, then return NULL.
103797 */
103798 #ifndef SQLITE_OMIT_SUBQUERY
103799 static Select *isCandidateForInOpt(const Expr *pX){
103800 Select *p;
103801 SrcList *pSrc;
103802 ExprList *pEList;
103803 Table *pTab;
103804 int i;
@@ -104168,11 +104172,11 @@
104172 ** the affinities to be used for each column of the comparison.
104173 **
104174 ** It is the responsibility of the caller to ensure that the returned
104175 ** string is eventually freed using sqlite3DbFree().
104176 */
104177 static char *exprINAffinity(Parse *pParse, const Expr *pExpr){
104178 Expr *pLeft = pExpr->pLeft;
104179 int nVal = sqlite3ExprVectorSize(pLeft);
104180 Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0;
104181 char *zRet;
104182
@@ -106087,11 +106091,11 @@
106091 assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
106092 if( pParse->pVdbe==0 ) return;
106093 inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
106094 if( inReg!=target ){
106095 u8 op;
106096 if( ALWAYS(pExpr) && ExprHasProperty(pExpr,EP_Subquery) ){
106097 op = OP_Copy;
106098 }else{
106099 op = OP_SCopy;
106100 }
106101 sqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target);
@@ -106625,11 +106629,15 @@
106629 ** Additionally, if pExpr is a simple SQL value and the value is the
106630 ** same as that currently bound to variable pVar, non-zero is returned.
106631 ** Otherwise, if the values are not the same or if pExpr is not a simple
106632 ** SQL value, zero is returned.
106633 */
106634 static int exprCompareVariable(
106635 const Parse *pParse,
106636 const Expr *pVar,
106637 const Expr *pExpr
106638 ){
106639 int res = 0;
106640 int iVar;
106641 sqlite3_value *pL, *pR = 0;
106642
106643 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
@@ -106677,11 +106685,16 @@
106685 ** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
106686 ** If pParse is NULL (the normal case) then any TK_VARIABLE term in
106687 ** Argument pParse should normally be NULL. If it is not NULL and pA or
106688 ** pB causes a return value of 2.
106689 */
106690 SQLITE_PRIVATE int sqlite3ExprCompare(
106691 const Parse *pParse,
106692 const Expr *pA,
106693 const Expr *pB,
106694 int iTab
106695 ){
106696 u32 combinedFlags;
106697 if( pA==0 || pB==0 ){
106698 return pB==pA ? 0 : 2;
106699 }
106700 if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
@@ -106761,11 +106774,11 @@
106774 ** a malfunction will result.
106775 **
106776 ** Two NULL pointers are considered to be the same. But a NULL pointer
106777 ** always differs from a non-NULL pointer.
106778 */
106779 SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){
106780 int i;
106781 if( pA==0 && pB==0 ) return 0;
106782 if( pA==0 || pB==0 ) return 1;
106783 if( pA->nExpr!=pB->nExpr ) return 1;
106784 for(i=0; i<pA->nExpr; i++){
@@ -106780,11 +106793,11 @@
106793
106794 /*
106795 ** Like sqlite3ExprCompare() except COLLATE operators at the top-level
106796 ** are ignored.
106797 */
106798 SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){
106799 return sqlite3ExprCompare(0,
106800 sqlite3ExprSkipCollateAndLikely(pA),
106801 sqlite3ExprSkipCollateAndLikely(pB),
106802 iTab);
106803 }
@@ -106794,13 +106807,13 @@
106807 **
106808 ** Or if seenNot is true, return non-zero if Expr p can only be
106809 ** non-NULL if pNN is not NULL
106810 */
106811 static int exprImpliesNotNull(
106812 const Parse *pParse,/* Parsing context */
106813 const Expr *p, /* The expression to be checked */
106814 const Expr *pNN, /* The expression that is NOT NULL */
106815 int iTab, /* Table being evaluated */
106816 int seenNot /* Return true only if p can be any non-NULL value */
106817 ){
106818 assert( p );
106819 assert( pNN );
@@ -106889,11 +106902,16 @@
106902 **
106903 ** When in doubt, return false. Returning true might give a performance
106904 ** improvement. Returning false might cause a performance reduction, but
106905 ** it will always give the correct answer and is hence always safe.
106906 */
106907 SQLITE_PRIVATE int sqlite3ExprImpliesExpr(
106908 const Parse *pParse,
106909 const Expr *pE1,
106910 const Expr *pE2,
106911 int iTab
106912 ){
106913 if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){
106914 return 1;
106915 }
106916 if( pE2->op==TK_OR
106917 && (sqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab)
@@ -108220,11 +108238,11 @@
108238 ** After the parse finishes, renameTokenFind() routine can be used
108239 ** to look up the actual token value that created some element in
108240 ** the parse tree.
108241 */
108242 struct RenameToken {
108243 const void *p; /* Parse tree element created by token t */
108244 Token t; /* The token that created parse tree element p */
108245 RenameToken *pNext; /* Next is a list of all RenameToken objects */
108246 };
108247
108248 /*
@@ -108262,13 +108280,13 @@
108280 ** if( x==y ) ...
108281 **
108282 ** Technically, as x no longer points into a valid object or to the byte
108283 ** following a valid object, it may not be used in comparison operations.
108284 */
108285 static void renameTokenCheckAll(Parse *pParse, const void *pPtr){
108286 if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){
108287 const RenameToken *p;
108288 u8 i = 0;
108289 for(p=pParse->pRename; p; p=p->pNext){
108290 if( p->p ){
108291 assert( p->p!=pPtr );
108292 i += *(u8*)(p->p);
@@ -108290,11 +108308,15 @@
108308 **
108309 ** The pPtr argument is returned so that this routine can be used
108310 ** with tail recursion in tokenExpr() routine, for a small performance
108311 ** improvement.
108312 */
108313 SQLITE_PRIVATE const void *sqlite3RenameTokenMap(
108314 Parse *pParse,
108315 const void *pPtr,
108316 const Token *pToken
108317 ){
108318 RenameToken *pNew;
108319 assert( pPtr || pParse->db->mallocFailed );
108320 renameTokenCheckAll(pParse, pPtr);
108321 if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){
108322 pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken));
@@ -108312,11 +108334,11 @@
108334 /*
108335 ** It is assumed that there is already a RenameToken object associated
108336 ** with parse tree element pFrom. This function remaps the associated token
108337 ** to parse tree element pTo.
108338 */
108339 SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){
108340 RenameToken *p;
108341 renameTokenCheckAll(pParse, pTo);
108342 for(p=pParse->pRename; p; p=p->pNext){
108343 if( p->p==pFrom ){
108344 p->p = pTo;
@@ -108328,11 +108350,12 @@
108350 /*
108351 ** Walker callback used by sqlite3RenameExprUnmap().
108352 */
108353 static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){
108354 Parse *pParse = pWalker->pParse;
108355 sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr);
108356 sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab);
108357 return WRC_Continue;
108358 }
108359
108360 /*
108361 ** Iterate through the Select objects that are part of WITH clauses attached
@@ -108358,10 +108381,11 @@
108381 Select *p = pWith->a[i].pSelect;
108382 NameContext sNC;
108383 memset(&sNC, 0, sizeof(sNC));
108384 sNC.pParse = pParse;
108385 if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC);
108386 if( sNC.pParse->db->mallocFailed ) return;
108387 sqlite3WalkSelect(pWalker, p);
108388 sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols);
108389 }
108390 if( pCopy && pParse->pWith==pCopy ){
108391 pParse->pWith = pCopy->pOuter;
@@ -108372,16 +108396,16 @@
108396 /*
108397 ** Unmap all tokens in the IdList object passed as the second argument.
108398 */
108399 static void unmapColumnIdlistNames(
108400 Parse *pParse,
108401 const IdList *pIdList
108402 ){
108403 if( pIdList ){
108404 int ii;
108405 for(ii=0; ii<pIdList->nId; ii++){
108406 sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName);
108407 }
108408 }
108409 }
108410
108411 /*
@@ -108389,13 +108413,11 @@
108413 */
108414 static int renameUnmapSelectCb(Walker *pWalker, Select *p){
108415 Parse *pParse = pWalker->pParse;
108416 int i;
108417 if( pParse->nErr ) return WRC_Abort;
108418 if( NEVER(p->selFlags & (SF_View|SF_CopyCte)) ){
 
 
108419 return WRC_Prune;
108420 }
108421 if( ALWAYS(p->pEList) ){
108422 ExprList *pList = p->pEList;
108423 for(i=0; i<pList->nExpr; i++){
@@ -108406,11 +108428,11 @@
108428 }
108429 if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */
108430 SrcList *pSrc = p->pSrc;
108431 for(i=0; i<pSrc->nSrc; i++){
108432 sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName);
108433 sqlite3WalkExpr(pWalker, pSrc->a[i].pOn);
108434 unmapColumnIdlistNames(pParse, pSrc->a[i].pUsing);
108435 }
108436 }
108437
108438 renameWalkWith(pWalker, p);
@@ -108474,11 +108496,11 @@
108496 ** the list maintained by the RenameCtx object.
108497 */
108498 static RenameToken *renameTokenFind(
108499 Parse *pParse,
108500 struct RenameCtx *pCtx,
108501 const void *pPtr
108502 ){
108503 RenameToken **pp;
108504 if( NEVER(pPtr==0) ){
108505 return 0;
108506 }
@@ -108593,22 +108615,22 @@
108615 ** to the RenameCtx pCtx.
108616 */
108617 static void renameColumnElistNames(
108618 Parse *pParse,
108619 RenameCtx *pCtx,
108620 const ExprList *pEList,
108621 const char *zOld
108622 ){
108623 if( pEList ){
108624 int i;
108625 for(i=0; i<pEList->nExpr; i++){
108626 const char *zName = pEList->a[i].zEName;
108627 if( ALWAYS(pEList->a[i].eEName==ENAME_NAME)
108628 && ALWAYS(zName!=0)
108629 && 0==sqlite3_stricmp(zName, zOld)
108630 ){
108631 renameTokenFind(pParse, pCtx, (const void*)zName);
108632 }
108633 }
108634 }
108635 }
108636
@@ -108618,19 +108640,19 @@
108640 ** from Parse object pParse and add it to the RenameCtx pCtx.
108641 */
108642 static void renameColumnIdlistNames(
108643 Parse *pParse,
108644 RenameCtx *pCtx,
108645 const IdList *pIdList,
108646 const char *zOld
108647 ){
108648 if( pIdList ){
108649 int i;
108650 for(i=0; i<pIdList->nId; i++){
108651 const char *zName = pIdList->a[i].zName;
108652 if( 0==sqlite3_stricmp(zName, zOld) ){
108653 renameTokenFind(pParse, pCtx, (const void*)zName);
108654 }
108655 }
108656 }
108657 }
108658
@@ -109326,11 +109348,11 @@
109348 return;
109349 }
109350
109351 static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){
109352 if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){
109353 renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr);
109354 }
109355 return WRC_Continue;
109356 }
109357
109358 /*
@@ -109596,11 +109618,11 @@
109618 ** ALTER TABLE pSrc DROP COLUMN pName
109619 **
109620 ** statement. Argument pSrc contains the possibly qualified name of the
109621 ** table being edited, and token pName the name of the column to drop.
109622 */
109623 SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){
109624 sqlite3 *db = pParse->db; /* Database handle */
109625 Table *pTab; /* Table to modify */
109626 int iDb; /* Index of db containing pTab in aDb[] */
109627 const char *zDb; /* Database containing pTab ("main" etc.) */
109628 char *zCol = 0; /* Name of column to drop */
@@ -110181,11 +110203,10 @@
110203 n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */
110204 + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */
110205 + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample);
110206 }
110207 #endif
 
110208 p = sqlite3DbMallocZero(db, n);
110209 if( p==0 ){
110210 sqlite3_result_error_nomem(context);
110211 return;
110212 }
@@ -110600,32 +110621,23 @@
110621 ** If D is the count of distinct values and K is the total number of
110622 ** rows, then each estimate is computed as:
110623 **
110624 ** I = (K+D-1)/D
110625 */
110626 sqlite3_str sStat; /* Text of the constructed "stat" line */
110627 int i; /* Loop counter */
110628
110629 sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100);
110630 sqlite3_str_appendf(&sStat, "%llu",
110631 p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow);
 
 
 
 
 
 
110632 for(i=0; i<p->nKeyCol; i++){
110633 u64 nDistinct = p->current.anDLt[i] + 1;
110634 u64 iVal = (p->nRow + nDistinct - 1) / nDistinct;
110635 sqlite3_str_appendf(&sStat, " %llu", iVal);
 
110636 assert( p->current.anEq[i] );
110637 }
110638 sqlite3ResultStrAccum(context, &sStat);
 
 
110639 }
110640 #ifdef SQLITE_ENABLE_STAT4
110641 else if( eCall==STAT_GET_ROWID ){
110642 if( p->iGet<0 ){
110643 samplePushPrevious(p, 0);
@@ -110640,10 +110652,12 @@
110652 SQLITE_TRANSIENT);
110653 }
110654 }
110655 }else{
110656 tRowcnt *aCnt = 0;
110657 sqlite3_str sStat;
110658 int i;
110659
110660 assert( p->iGet<p->nSample );
110661 switch( eCall ){
110662 case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break;
110663 case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break;
@@ -110651,27 +110665,16 @@
110665 aCnt = p->a[p->iGet].anDLt;
110666 p->iGet++;
110667 break;
110668 }
110669 }
110670 sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100);
110671 for(i=0; i<p->nCol; i++){
110672 sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]);
110673 }
110674 if( sStat.nChar ) sStat.nChar--;
110675 sqlite3ResultStrAccum(context, &sStat);
 
 
 
 
 
 
 
 
 
 
 
110676 }
110677 #endif /* SQLITE_ENABLE_STAT4 */
110678 #ifndef SQLITE_DEBUG
110679 UNUSED_PARAMETER( argc );
110680 #endif
@@ -111588,13 +111591,16 @@
111591 ** Load content from the sqlite_stat4 table into
111592 ** the Index.aSample[] arrays of all indices.
111593 */
111594 static int loadStat4(sqlite3 *db, const char *zDb){
111595 int rc = SQLITE_OK; /* Result codes from subroutines */
111596 const Table *pStat4;
111597
111598 assert( db->lookaside.bDisable );
111599 if( (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0
111600 && IsOrdinaryTable(pStat4)
111601 ){
111602 rc = loadStatTbl(db,
111603 "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx",
111604 "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4",
111605 zDb
111606 );
@@ -111627,10 +111633,11 @@
111633 analysisInfo sInfo;
111634 HashElem *i;
111635 char *zSql;
111636 int rc = SQLITE_OK;
111637 Schema *pSchema = db->aDb[iDb].pSchema;
111638 const Table *pStat1;
111639
111640 assert( iDb>=0 && iDb<db->nDb );
111641 assert( db->aDb[iDb].pBt!=0 );
111642
111643 /* Clear any prior statistics */
@@ -111649,11 +111656,13 @@
111656 }
111657
111658 /* Load new statistics out of the sqlite_stat1 table */
111659 sInfo.db = db;
111660 sInfo.zDatabase = db->aDb[iDb].zDbSName;
111661 if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase))
111662 && IsOrdinaryTable(pStat1)
111663 ){
111664 zSql = sqlite3MPrintf(db,
111665 "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
111666 if( zSql==0 ){
111667 rc = SQLITE_NOMEM_BKPT;
111668 }else{
@@ -113453,14 +113462,14 @@
113462 **
113463 ** Tokens are often just pointers into the original SQL text and so
113464 ** are not \000 terminated and are not persistent. The returned string
113465 ** is \000 terminated and is persistent.
113466 */
113467 SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){
113468 char *zName;
113469 if( pName ){
113470 zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n);
113471 sqlite3Dequote(zName);
113472 }else{
113473 zName = 0;
113474 }
113475 return zName;
@@ -116947,11 +116956,11 @@
116956 if( pTab ){
116957 /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list.
116958 ** The list was already ordered when this routine was entered, so at this
116959 ** point at most a single index (the newly added index) will be out of
116960 ** order. So we have to reorder at most one index. */
116961 Index **ppFrom;
116962 Index *pThis;
116963 for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){
116964 Index *pNext;
116965 if( pThis->onError!=OE_Replace ) continue;
116966 while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){
@@ -121358,101 +121367,167 @@
121367 minMaxValueFinalize(context, 0);
121368 }
121369
121370 /*
121371 ** group_concat(EXPR, ?SEPARATOR?)
121372 **
121373 ** The SEPARATOR goes before the EXPR string. This is tragic. The
121374 ** groupConcatInverse() implementation would have been easier if the
121375 ** SEPARATOR were appended after EXPR. And the order is undocumented,
121376 ** so we could change it, in theory. But the old behavior has been
121377 ** around for so long that we dare not, for fear of breaking something.
121378 */
121379 typedef struct {
121380 StrAccum str; /* The accumulated concatenation */
121381 #ifndef SQLITE_OMIT_WINDOWFUNC
121382 int nAccum; /* Number of strings presently concatenated */
121383 int nFirstSepLength; /* Used to detect separator length change */
121384 /* If pnSepLengths!=0, refs an array of inter-string separator lengths,
121385 ** stored as actually incorporated into presently accumulated result.
121386 ** (Hence, its slots in use number nAccum-1 between method calls.)
121387 ** If pnSepLengths==0, nFirstSepLength is the length used throughout.
121388 */
121389 int *pnSepLengths;
121390 #endif
121391 } GroupConcatCtx;
121392
121393 static void groupConcatStep(
121394 sqlite3_context *context,
121395 int argc,
121396 sqlite3_value **argv
121397 ){
121398 const char *zVal;
121399 GroupConcatCtx *pGCC;
121400 const char *zSep;
121401 int nVal, nSep;
121402 assert( argc==1 || argc==2 );
121403 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121404 pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
121405 if( pGCC ){
 
121406 sqlite3 *db = sqlite3_context_db_handle(context);
121407 int firstTerm = pGCC->str.mxAlloc==0;
121408 pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
121409 if( argc==1 ){
121410 if( !firstTerm ){
121411 sqlite3_str_appendchar(&pGCC->str, 1, ',');
121412 }
121413 #ifndef SQLITE_OMIT_WINDOWFUNC
121414 else{
121415 pGCC->nFirstSepLength = 1;
121416 }
121417 #endif
121418 }else if( !firstTerm ){
121419 zSep = (char*)sqlite3_value_text(argv[1]);
121420 nSep = sqlite3_value_bytes(argv[1]);
121421 if( zSep ){
121422 sqlite3_str_append(&pGCC->str, zSep, nSep);
121423 }
121424 #ifndef SQLITE_OMIT_WINDOWFUNC
121425 else{
121426 nSep = 0;
121427 }
121428 if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){
121429 int *pnsl = pGCC->pnSepLengths;
121430 if( pnsl == 0 ){
121431 /* First separator length variation seen, start tracking them. */
121432 pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int));
121433 if( pnsl!=0 ){
121434 int i = 0, nA = pGCC->nAccum-1;
121435 while( i<nA ) pnsl[i++] = pGCC->nFirstSepLength;
121436 }
121437 }else{
121438 pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int));
121439 }
121440 if( pnsl!=0 ){
121441 if( ALWAYS(pGCC->nAccum>0) ){
121442 pnsl[pGCC->nAccum-1] = nSep;
121443 }
121444 pGCC->pnSepLengths = pnsl;
121445 }else{
121446 sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM);
121447 }
121448 }
121449 #endif
121450 }
121451 #ifndef SQLITE_OMIT_WINDOWFUNC
121452 else{
121453 pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]);
121454 }
121455 pGCC->nAccum += 1;
121456 #endif
121457 zVal = (char*)sqlite3_value_text(argv[0]);
121458 nVal = sqlite3_value_bytes(argv[0]);
121459 if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal);
121460 }
121461 }
121462
121463 #ifndef SQLITE_OMIT_WINDOWFUNC
121464 static void groupConcatInverse(
121465 sqlite3_context *context,
121466 int argc,
121467 sqlite3_value **argv
121468 ){
121469 GroupConcatCtx *pGCC;
 
121470 assert( argc==1 || argc==2 );
121471 (void)argc; /* Suppress unused parameter warning */
121472 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
121473 pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
121474 /* pGCC is always non-NULL since groupConcatStep() will have always
121475 ** run frist to initialize it */
121476 if( ALWAYS(pGCC) ){
121477 int nVS = sqlite3_value_bytes(argv[0]);
121478 pGCC->nAccum -= 1;
121479 if( pGCC->pnSepLengths!=0 ){
121480 assert(pGCC->nAccum >= 0);
121481 if( pGCC->nAccum>0 ){
121482 nVS += *pGCC->pnSepLengths;
121483 memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1,
121484 (pGCC->nAccum-1)*sizeof(int));
121485 }
121486 }else{
121487 /* If removing single accumulated string, harmlessly over-do. */
121488 nVS += pGCC->nFirstSepLength;
121489 }
121490 if( nVS>=(int)pGCC->str.nChar ){
121491 pGCC->str.nChar = 0;
121492 }else{
121493 pGCC->str.nChar -= nVS;
121494 memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar);
121495 }
121496 if( pGCC->str.nChar==0 ){
121497 pGCC->str.mxAlloc = 0;
121498 sqlite3_free(pGCC->pnSepLengths);
121499 pGCC->pnSepLengths = 0;
121500 }
121501 }
121502 }
121503 #else
121504 # define groupConcatInverse 0
121505 #endif /* SQLITE_OMIT_WINDOWFUNC */
121506 static void groupConcatFinalize(sqlite3_context *context){
121507 GroupConcatCtx *pGCC
121508 = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
121509 if( pGCC ){
121510 sqlite3ResultStrAccum(context, &pGCC->str);
121511 #ifndef SQLITE_OMIT_WINDOWFUNC
121512 sqlite3_free(pGCC->pnSepLengths);
121513 #endif
 
 
 
 
121514 }
121515 }
121516 #ifndef SQLITE_OMIT_WINDOWFUNC
121517 static void groupConcatValue(sqlite3_context *context){
121518 GroupConcatCtx *pGCC
121519 = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
121520 if( pGCC ){
121521 StrAccum *pAccum = &pGCC->str;
121522 if( pAccum->accError==SQLITE_TOOBIG ){
121523 sqlite3_result_error_toobig(context);
121524 }else if( pAccum->accError==SQLITE_NOMEM ){
121525 sqlite3_result_error_nomem(context);
121526 }else{
121527 const char *zText = sqlite3_str_value(pAccum);
121528 sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT);
121529 }
121530 }
121531 }
121532 #else
121533 # define groupConcatValue 0
@@ -131668,11 +131743,11 @@
131743 if( sqlite3Config.bExtraSchemaChecks ){
131744 corruptSchema(pData, argv, "invalid rootpage");
131745 }
131746 }
131747 db->init.orphanTrigger = 0;
131748 db->init.azInit = (const char**)argv;
131749 pStmt = 0;
131750 TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0);
131751 rc = db->errCode;
131752 assert( (rc&0xFF)==(rcp&0xFF) );
131753 db->init.iDb = saved_iDb;
@@ -131687,10 +131762,11 @@
131762 }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){
131763 corruptSchema(pData, argv, sqlite3_errmsg(db));
131764 }
131765 }
131766 }
131767 db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */
131768 sqlite3_finalize(pStmt);
131769 }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){
131770 corruptSchema(pData, argv, 0);
131771 }else{
131772 /* If the SQL column is blank it means this is an index that
@@ -134990,11 +135066,11 @@
135066 SelectDest *pDest /* What to do with query results */
135067 ){
135068 SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */
135069 int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */
135070 Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */
135071 Select *pSetup; /* The setup query */
135072 Select *pFirstRec; /* Left-most recursive term */
135073 int addrTop; /* Top of the loop */
135074 int addrCont, addrBreak; /* CONTINUE and BREAK addresses */
135075 int iCurrent = 0; /* The Current table */
135076 int regCurrent; /* Register holding Current table */
@@ -135074,11 +135150,10 @@
135150 ** functions. Mark the recursive elements as UNION ALL even if they
135151 ** are really UNION because the distinctness will be enforced by the
135152 ** iDistinct table. pFirstRec is left pointing to the left-most
135153 ** recursive term of the CTE.
135154 */
 
135155 for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){
135156 if( pFirstRec->selFlags & SF_Aggregate ){
135157 sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
135158 goto end_of_recursive_query;
135159 }
@@ -138055,13 +138130,13 @@
138130 ){
138131 sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
138132 pTab->zName);
138133 }
138134 pFrom->pSelect = sqlite3SelectDup(db, pTab->u.view.pSelect, 0);
138135 }
138136 #ifndef SQLITE_OMIT_VIRTUALTABLE
138137 else if( ALWAYS(IsVirtual(pTab))
138138 && pFrom->fg.fromDDL
138139 && ALWAYS(pTab->u.vtab.p!=0)
138140 && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0)
138141 ){
138142 sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"",
@@ -144520,10 +144595,11 @@
144595 VtabCtx *pCtx;
144596 int rc = SQLITE_OK;
144597 Table *pTab;
144598 char *zErr = 0;
144599 Parse sParse;
144600 int initBusy;
144601
144602 #ifdef SQLITE_ENABLE_API_ARMOR
144603 if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){
144604 return SQLITE_MISUSE_BKPT;
144605 }
@@ -144539,10 +144615,16 @@
144615 assert( IsVirtual(pTab) );
144616
144617 memset(&sParse, 0, sizeof(sParse));
144618 sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
144619 sParse.db = db;
144620 /* We should never be able to reach this point while loading the
144621 ** schema. Nevertheless, defend against that (turn off db->init.busy)
144622 ** in case a bug arises. */
144623 assert( db->init.busy==0 );
144624 initBusy = db->init.busy;
144625 db->init.busy = 0;
144626 sParse.nQueryLoop = 1;
144627 if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr)
144628 && sParse.pNewTable
144629 && !db->mallocFailed
144630 && IsOrdinaryTable(sParse.pNewTable)
@@ -144585,10 +144667,11 @@
144667 if( sParse.pVdbe ){
144668 sqlite3VdbeFinalize(sParse.pVdbe);
144669 }
144670 sqlite3DeleteTable(db, sParse.pNewTable);
144671 sqlite3ParserReset(&sParse);
144672 db->init.busy = initBusy;
144673
144674 assert( (rc&0xff)==rc );
144675 rc = sqlite3ApiExit(db, rc);
144676 sqlite3_mutex_leave(db->mutex);
144677 return rc;
@@ -154396,10 +154479,11 @@
154479 WhereLoop *pLoop;
154480 int iCur;
154481 int j;
154482 Table *pTab;
154483 Index *pIdx;
154484 WhereScan scan;
154485
154486 pWInfo = pBuilder->pWInfo;
154487 if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0;
154488 assert( pWInfo->pTabList->nSrc>=1 );
154489 pItem = pWInfo->pTabList->a;
@@ -154409,13 +154493,14 @@
154493 iCur = pItem->iCursor;
154494 pWC = &pWInfo->sWC;
154495 pLoop = pBuilder->pNew;
154496 pLoop->wsFlags = 0;
154497 pLoop->nSkip = 0;
154498 pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0);
154499 if( pTerm ){
154500 testcase( pTerm->eOperator & WO_IS );
154501 assert( pTerm->prereqRight==0 );
154502 pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
154503 pLoop->aLTerm[0] = pTerm;
154504 pLoop->nLTerm = 1;
154505 pLoop->u.btree.nEq = 1;
154506 /* TUNING: Cost of a rowid lookup is 10 */
@@ -154428,11 +154513,12 @@
154513 || pIdx->pPartIdxWhere!=0
154514 || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace)
154515 ) continue;
154516 opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ;
154517 for(j=0; j<pIdx->nKeyCol; j++){
154518 pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx);
154519 while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan);
154520 if( pTerm==0 ) break;
154521 testcase( pTerm->eOperator & WO_IS );
154522 pLoop->aLTerm[j] = pTerm;
154523 }
154524 if( j!=pIdx->nKeyCol ) continue;
@@ -154457,12 +154543,18 @@
154543 pWInfo->nRowOut = 1;
154544 if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr;
154545 if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){
154546 pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
154547 }
154548 if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS;
154549 #ifdef SQLITE_DEBUG
154550 pLoop->cId = '0';
154551 #endif
154552 #ifdef WHERETRACE_ENABLED
154553 if( sqlite3WhereTrace ){
154554 sqlite3DebugPrintf("whereShortCut() used to compute solution\n");
154555 }
154556 #endif
154557 return 1;
154558 }
154559 return 0;
154560 }
@@ -156839,11 +156931,16 @@
156931 /*
156932 ** Return 0 if the two window objects are identical, 1 if they are
156933 ** different, or 2 if it cannot be determined if the objects are identical
156934 ** or not. Identical window objects can be processed in a single scan.
156935 */
156936 SQLITE_PRIVATE int sqlite3WindowCompare(
156937 const Parse *pParse,
156938 const Window *p1,
156939 const Window *p2,
156940 int bFilter
156941 ){
156942 int res;
156943 if( NEVER(p1==0) || NEVER(p2==0) ) return 1;
156944 if( p1->eFrmType!=p2->eFrmType ) return 1;
156945 if( p1->eStart!=p2->eStart ) return 1;
156946 if( p1->eEnd!=p2->eEnd ) return 1;
@@ -168909,10 +169006,11 @@
169006 db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
169007 db->autoCommit = 1;
169008 db->nextAutovac = -1;
169009 db->szMmap = sqlite3GlobalConfig.szMmap;
169010 db->nextPagesize = 0;
169011 db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */
169012 #ifdef SQLITE_ENABLE_SORTER_MMAP
169013 /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map
169014 ** the temporary files used to do external sorts (see code in vdbesort.c)
169015 ** is disabled. It can still be used either by defining
169016 ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the
@@ -170190,13 +170288,15 @@
170288 ** passing free() a pointer that was not obtained from malloc() - it is
170289 ** an error that we cannot easily detect but that will likely cause memory
170290 ** corruption.
170291 */
170292 SQLITE_API const char *sqlite3_filename_database(const char *zFilename){
170293 if( zFilename==0 ) return 0;
170294 return databaseName(zFilename);
170295 }
170296 SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){
170297 if( zFilename==0 ) return 0;
170298 zFilename = databaseName(zFilename);
170299 zFilename += sqlite3Strlen30(zFilename) + 1;
170300 while( zFilename[0] ){
170301 zFilename += sqlite3Strlen30(zFilename) + 1;
170302 zFilename += sqlite3Strlen30(zFilename) + 1;
@@ -170206,11 +170306,11 @@
170306 SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){
170307 #ifdef SQLITE_OMIT_WAL
170308 return 0;
170309 #else
170310 zFilename = sqlite3_filename_journal(zFilename);
170311 if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1;
170312 return zFilename;
170313 #endif
170314 }
170315
170316 /*
@@ -190887,11 +190987,11 @@
190987 }
190988 if( pNode->u.zJContent[0]=='-' ){ i = -i; }
190989 sqlite3_result_int64(pCtx, i);
190990 int_done:
190991 break;
190992 int_as_real: ; /* no break */ deliberate_fall_through
190993 }
190994 case JSON_REAL: {
190995 double r;
190996 #ifdef SQLITE_AMALGAMATION
190997 const char *z = pNode->u.zJContent;
@@ -195462,10 +195562,14 @@
195562 ){
195563 int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64);
195564 xSetMapping = ((iHeight==0)?rowidWrite:parentWrite);
195565 if( iHeight>0 ){
195566 RtreeNode *pChild = nodeHashLookup(pRtree, iRowid);
195567 RtreeNode *p;
195568 for(p=pNode; p; p=p->pParent){
195569 if( p==pChild ) return SQLITE_CORRUPT_VTAB;
195570 }
195571 if( pChild ){
195572 nodeRelease(pRtree, pChild->pParent);
195573 nodeReference(pNode);
195574 pChild->pParent = pNode;
195575 }
@@ -206052,10 +206156,19 @@
206156
206157 /* #include "sqliteInt.h" ** Requires access to internal data structures ** */
206158 #if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \
206159 && !defined(SQLITE_OMIT_VIRTUALTABLE)
206160
206161 /*
206162 ** The pager and btree modules arrange objects in memory so that there are
206163 ** always approximately 200 bytes of addressable memory following each page
206164 ** buffer. This way small buffer overreads caused by corrupt database pages
206165 ** do not cause undefined behaviour. This module pads each page buffer
206166 ** by the following number of bytes for the same purpose.
206167 */
206168 #define DBSTAT_PAGE_PADDING_BYTES 256
206169
206170 /*
206171 ** Page paths:
206172 **
206173 ** The value of the 'path' column describes the path taken from the
206174 ** root-node of the b-tree structure to each page. The value of the
@@ -206119,13 +206232,12 @@
206232 };
206233
206234 /* Size information for a single btree page */
206235 struct StatPage {
206236 u32 iPgno; /* Page number */
206237 u8 *aPg; /* Page buffer from sqlite3_malloc() */
206238 int iCell; /* Current cell */
 
206239 char *zPath; /* Path to this page */
206240
206241 /* Variables populated by statDecodePage(): */
206242 u8 flags; /* Copy of flags byte */
206243 int nCell; /* Number of cells on page */
@@ -206333,22 +206445,29 @@
206445 p->nCell = 0;
206446 p->aCell = 0;
206447 }
206448
206449 static void statClearPage(StatPage *p){
206450 u8 *aPg = p->aPg;
206451 statClearCells(p);
 
206452 sqlite3_free(p->zPath);
206453 memset(p, 0, sizeof(StatPage));
206454 p->aPg = aPg;
206455 }
206456
206457 static void statResetCsr(StatCursor *pCsr){
206458 int i;
206459 /* In some circumstances, specifically if an OOM has occurred, the call
206460 ** to sqlite3_reset() may cause the pager to be reset (emptied). It is
206461 ** important that statClearPage() is called to free any page refs before
206462 ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */
206463 for(i=0; i<ArraySize(pCsr->aPage); i++){
206464 statClearPage(&pCsr->aPage[i]);
206465 sqlite3_free(pCsr->aPage[i].aPg);
206466 pCsr->aPage[i].aPg = 0;
206467 }
206468 sqlite3_reset(pCsr->pStmt);
206469 pCsr->iPage = 0;
206470 sqlite3_free(pCsr->zPath);
206471 pCsr->zPath = 0;
206472 pCsr->isEof = 0;
206473 }
@@ -206409,11 +206528,11 @@
206528 int iOff;
206529 int nHdr;
206530 int isLeaf;
206531 int szPage;
206532
206533 u8 *aData = p->aPg;
206534 u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
206535
206536 p->flags = aHdr[0];
206537 if( p->flags==0x0A || p->flags==0x0D ){
206538 isLeaf = 1;
@@ -206480,11 +206599,11 @@
206599 assert( nPayload>=(u32)nLocal );
206600 assert( nLocal<=(nUsable-35) );
206601 if( nPayload>(u32)nLocal ){
206602 int j;
206603 int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
206604 if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){
206605 goto statPageIsCorrupt;
206606 }
206607 pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
206608 pCell->nOvfl = nOvfl;
206609 pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
@@ -206538,10 +206657,42 @@
206657 /* Not ZIPVFS: The default page size and offset */
206658 pCsr->szPage += sqlite3BtreeGetPageSize(pBt);
206659 pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
206660 }
206661 }
206662
206663 /*
206664 ** Load a copy of the page data for page iPg into the buffer belonging
206665 ** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK
206666 ** if successful, or an SQLite error code otherwise.
206667 */
206668 static int statGetPage(
206669 Btree *pBt, /* Load page from this b-tree */
206670 u32 iPg, /* Page number to load */
206671 StatPage *pPg /* Load page into this object */
206672 ){
206673 int pgsz = sqlite3BtreeGetPageSize(pBt);
206674 DbPage *pDbPage = 0;
206675 int rc;
206676
206677 if( pPg->aPg==0 ){
206678 pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES);
206679 if( pPg->aPg==0 ){
206680 return SQLITE_NOMEM_BKPT;
206681 }
206682 memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES);
206683 }
206684
206685 rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0);
206686 if( rc==SQLITE_OK ){
206687 const u8 *a = sqlite3PagerGetData(pDbPage);
206688 memcpy(pPg->aPg, a, pgsz);
206689 sqlite3PagerUnref(pDbPage);
206690 }
206691
206692 return rc;
206693 }
206694
206695 /*
206696 ** Move a DBSTAT cursor to the next entry. Normally, the next
206697 ** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0),
206698 ** the next entry is the next btree.
@@ -206557,11 +206708,11 @@
206708
206709 sqlite3_free(pCsr->zPath);
206710 pCsr->zPath = 0;
206711
206712 statNextRestart:
206713 if( pCsr->iPage<0 ){
206714 /* Start measuring space on the next btree */
206715 statResetCounts(pCsr);
206716 rc = sqlite3_step(pCsr->pStmt);
206717 if( rc==SQLITE_ROW ){
206718 int nPage;
@@ -206569,11 +206720,11 @@
206720 sqlite3PagerPagecount(pPager, &nPage);
206721 if( nPage==0 ){
206722 pCsr->isEof = 1;
206723 return sqlite3_reset(pCsr->pStmt);
206724 }
206725 rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]);
206726 pCsr->aPage[0].iPgno = iRoot;
206727 pCsr->aPage[0].iCell = 0;
206728 if( !pCsr->isAgg ){
206729 pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
206730 if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206620,13 +206771,12 @@
206771 p->iCell++;
206772 }
206773
206774 if( !p->iRightChildPg || p->iCell>p->nCell ){
206775 statClearPage(p);
206776 pCsr->iPage--;
206777 if( pCsr->isAgg && pCsr->iPage<0 ){
 
206778 /* label-statNext-done: When computing aggregate space usage over
206779 ** an entire btree, this is the exit point from this function */
206780 return SQLITE_OK;
206781 }
206782 goto statNextRestart; /* Tail recursion */
@@ -206641,11 +206791,11 @@
206791 if( p->iCell==p->nCell ){
206792 p[1].iPgno = p->iRightChildPg;
206793 }else{
206794 p[1].iPgno = p->aCell[p->iCell].iChildPg;
206795 }
206796 rc = statGetPage(pBt, p[1].iPgno, &p[1]);
206797 pCsr->nPage++;
206798 p[1].iCell = 0;
206799 if( !pCsr->isAgg ){
206800 p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
206801 if( z==0 ) rc = SQLITE_NOMEM_BKPT;
@@ -206771,10 +206921,11 @@
206921 rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
206922 sqlite3_free(zSql);
206923 }
206924
206925 if( rc==SQLITE_OK ){
206926 pCsr->iPage = -1;
206927 rc = statNext(pCursor);
206928 }
206929 return rc;
206930 }
206931
@@ -222410,10 +222561,11 @@
222561 }
222562
222563 assert( (pRet==0)==(p->rc!=SQLITE_OK) );
222564 return pRet;
222565 }
222566
222567
222568 /*
222569 ** Release a reference to data record returned by an earlier call to
222570 ** fts5DataRead().
222571 */
@@ -223869,11 +224021,11 @@
224021 int pgnoLast = 0;
224022
224023 if( pDlidx ){
224024 int iSegid = pIter->pSeg->iSegid;
224025 pgnoLast = fts5DlidxIterPgno(pDlidx);
224026 pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
224027 }else{
224028 Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
224029
224030 /* Currently, Fts5SegIter.iLeafOffset points to the first byte of
224031 ** position-list content for the current rowid. Back it up so that it
@@ -223896,11 +224048,11 @@
224048
224049 /* The last rowid in the doclist may not be on the current page. Search
224050 ** forward to find the page containing the last rowid. */
224051 for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
224052 i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
224053 Fts5Data *pNew = fts5LeafRead(p, iAbs);
224054 if( pNew ){
224055 int iRowid, bTermless;
224056 iRowid = fts5LeafFirstRowidOff(pNew);
224057 bTermless = fts5LeafIsTermless(pNew);
224058 if( iRowid ){
@@ -223927,19 +224079,22 @@
224079 int iOff;
224080 fts5DataRelease(pIter->pLeaf);
224081 pIter->pLeaf = pLast;
224082 pIter->iLeafPgno = pgnoLast;
224083 iOff = fts5LeafFirstRowidOff(pLast);
224084 if( iOff>pLast->szLeaf ){
224085 p->rc = FTS5_CORRUPT;
224086 return;
224087 }
224088 iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
224089 pIter->iLeafOffset = iOff;
224090
224091 if( fts5LeafIsTermless(pLast) ){
224092 pIter->iEndofDoclist = pLast->nn+1;
224093 }else{
224094 pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast);
224095 }
 
224096 }
224097
224098 fts5SegIterReverseInitPage(p, pIter);
224099 }
224100
@@ -231295,11 +231450,11 @@
231450 int nArg, /* Number of args */
231451 sqlite3_value **apUnused /* Function arguments */
231452 ){
231453 assert( nArg==0 );
231454 UNUSED_PARAM2(nArg, apUnused);
231455 sqlite3_result_text(pCtx, "fts5: 2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813", -1, SQLITE_TRANSIENT);
231456 }
231457
231458 /*
231459 ** Return true if zName is the extension on one of the shadow tables used
231460 ** by this module.
231461
+1 -1
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -146,11 +146,11 @@
146146
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147147
** [sqlite_version()] and [sqlite_source_id()].
148148
*/
149149
#define SQLITE_VERSION "3.37.0"
150150
#define SQLITE_VERSION_NUMBER 3037000
151
-#define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
151
+#define SQLITE_SOURCE_ID "2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813"
152152
153153
/*
154154
** CAPI3REF: Run-Time Library Version Numbers
155155
** KEYWORDS: sqlite3_version sqlite3_sourceid
156156
**
157157
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -146,11 +146,11 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.37.0"
150 #define SQLITE_VERSION_NUMBER 3037000
151 #define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
--- src/sqlite3.h
+++ src/sqlite3.h
@@ -146,11 +146,11 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.37.0"
150 #define SQLITE_VERSION_NUMBER 3037000
151 #define SQLITE_SOURCE_ID "2021-10-04 11:10:15 8b24c177061c38361588f419eda9b7943b72a0c6b2855b6f39272451b8a1b813"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
+163 -46
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
1010
display: flex;
1111
flex-direction: column;
1212
border: none;
1313
align-items: flex-start;
1414
}
15
+body.chat button,
16
+body.chat input[type=button] {
17
+ line-height: inherit/*undo skin-specific funkiness*/;
18
+}
1519
body.chat .message-widget:last-of-type {
1620
/* Latest message: reduce bottom gap */
1721
margin-bottom: 0.1em;
1822
}
1923
body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
3640
min-width: 9em /*avoid unsightly "underlap" with the neighboring
3741
.message-widget-tab element*/;
3842
white-space: normal;
3943
}
4044
body.chat.monospace-messages .message-widget-content,
41
-body.chat.monospace-messages textarea,
42
-body.chat.monospace-messages input[type=text]{
45
+/*body.chat.monospace-messages textarea,*/
46
+/*body.chat.monospace-messages input[type=text],*/
47
+body.chat.monospace-messages #chat-input-field{
4348
font-family: monospace;
4449
}
4550
body.chat .message-widget-content > * {
4651
margin: 0;
4752
padding: 0;
@@ -127,11 +132,11 @@
127132
flex: 1 1 auto;
128133
}
129134
/* "Chat-only mode" hides the site header/footer, showing only
130135
the chat app. */
131136
body.chat.chat-only-mode{}
132
-body.chat #chat-settings-button {}
137
+body.chat #chat-button-settings {}
133138
/** Popup widget for the /chat settings. */
134139
body.chat .chat-settings-popup {
135140
font-size: 0.8em;
136141
text-align: left;
137142
display: flex;
@@ -168,86 +173,178 @@
168173
body.chat #chat-input-area {
169174
display: flex;
170175
flex-direction: column;
171176
padding: 0;
172177
margin: 0;
173
- position: initial /*sticky currently disabled due to scrolling-related issues*/;
174
- /*bottom: 0;*/
178
+ flex: 0 1 auto;
175179
}
176180
body.chat:not(.chat-only-mode) #chat-input-area{
177181
/* Safari user reports that 2em is necessary to keep the file selection
178182
widget from overlapping the page footer, whereas a margin of 0 is fine
179183
for FF/Chrome (and 2em is a *huge* waste of space for those). */
180184
margin-bottom: 0;
181185
}
182
-
186
+#chat-input-field {
187
+ display: inline-block/*supposed workaround for Chrome weirdness*/;
188
+ padding: 0.2em;
189
+ flex: 10 1 auto;
190
+ background-color: rgba(156,156,156,0.3);
191
+ overflow: auto;
192
+ resize: vertical;
193
+}
194
+#chat-input-field:empty::before {
195
+ content: attr(data-placeholder);
196
+ opacity: 0.6;
197
+}
198
+#chat-input-field:not(:focus){
199
+ border-width: 1px;
200
+ border-style: solid;
201
+ border-radius: 0.25em;
202
+}
203
+#chat-input-field:focus{
204
+ /* This transparent border helps avoid the text shifting around
205
+ when the contenteditable attribute causes a border (which we
206
+ apparently cannot style) to be added. */
207
+ border-width: 1px;
208
+ border-style: solid;
209
+ border-color: transparent;
210
+ border-radius: 0.25em;
211
+}
183212
/* Widget holding the chat message input field, send button, and
184213
settings button. */
185214
body.chat #chat-input-line {
186215
display: flex;
187216
flex-direction: row;
188217
align-items: stretch;
218
+ flex-wrap: nowrap;
219
+}
220
+/*body.chat #chat-input-line:not(.compact) {
221
+ flex-wrap: nowrap;
222
+}*/
223
+body.chat #chat-input-line.compact {
224
+ /* "The problem" with wrapping, together with a contenteditable input
225
+ field, is that the latter grows as the user types, so causes
226
+ wrapping to happen while they type, then to unwrap as soon as the
227
+ input field is cleared (when the message is sent). When we stay
228
+ wrapped in compact mode, the wrapped buttons simply take up too
229
+ much space. */
230
+ /*flex-wrap: wrap;
231
+ justify-content: flex-end;*/
232
+ flex-direction: column;
233
+ /**
234
+ We "really do" need column orientation here because it's the
235
+ only way to eliminate the possibility that (A) the buttons
236
+ get truncated in very narrow windows and (B) that they keep
237
+ stable positions.
238
+ */
189239
}
190
-body.chat #chat-input-line.single-line {
191
- flex-wrap: wrap;
240
+body.chat #chat-input-line.compact #chat-input-field {
192241
}
193
-body.chat #chat-edit-buttons {
194
- flex: 1 1 auto;
242
+
243
+body.chat #chat-buttons-wrapper {
244
+ flex: 0 1 auto;
195245
display: flex;
196246
flex-direction: column;
197
- justify-content: space-between;
198
-}
199
-body.chat #chat-input-line.single-line #chat-edit-buttons {
200
- flex-direction: row;
247
+ align-items: center;
248
+ min-width: 4em;
249
+ min-height: 1.5em;
250
+ align-self: flex-end
251
+ /*keep buttons stable at bottom/right even when input field
252
+ resizes */;
201253
}
202
-body.chat #chat-edit-buttons > * {
254
+body.chat #chat-input-line.compact #chat-buttons-wrapper {
255
+ flex-direction: row;
203256
flex: 1 1 auto;
204
- padding: initial/*some skins mess this up for buttons*/;
205
-}
206
-body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
207
- max-width: 4em;
208
- margin: 0.25em;
209
-}
210
-body.chat #chat-input-line.single-line #chat-edit-buttons > * {
211
- margin: 0 0.25em;
257
+ align-self: stretch;
258
+ justify-content: flex-end;
259
+ /*flex-wrap: wrap;*/
260
+ /* Wrapping would be ideal except that the edit widget
261
+ grows in width as the user types, moving the buttons
262
+ around */
263
+}
264
+body.chat #chat-buttons-wrapper > .cbutton {
265
+ padding: 0;
266
+ display: inline-block;
267
+ border-width: 1px;
268
+ border-style: solid;
269
+ border-radius: 0.25em;
270
+ min-width: 4ex;
271
+ max-width: 4ex;
272
+ min-height: 4ex;
273
+ max-height: 4ex;
274
+ margin: 0.125em;
275
+ display: inline-flex;
276
+ justify-content: center;
277
+ align-items: center;
278
+ cursor: pointer;
279
+ font-size: 130%;
280
+}
281
+body.chat #chat-buttons-wrapper > .cbutton:hover {
282
+ background-color: rgba(200,200,200,0.3);
283
+}
284
+body.chat #chat-input-line.compact #chat-buttons-wrapper > .cbutton {
285
+ margin: 2px 0.125em 0 0.125em;
286
+ min-width: 6ex;
287
+ max-width: 6ex;
288
+ min-height: 2.3ex;
289
+ max-height: 2.3ex;
290
+ font-size: 120%;
291
+}
292
+body.chat #chat-input-line.compact #chat-buttons-wrapper #chat-button-submit {
293
+ min-width: 12ex;
294
+}
295
+body.chat #chat-input-line:not(.compact) #chat-input-field {
296
+ /*border-left-style: double;
297
+ border-left-width: 3px;
298
+ border-right-style: double;
299
+ border-right-width: 3px;*/
300
+ min-height: 4rem;
301
+ /*max-height: 50rem;*/
302
+/*
303
+ Problems related to max-height:
304
+
305
+ - If we do NOT set a max-height then pasting/typing a large amount
306
+ of text can cause this element to grow without bounds, larger than
307
+ the window, and there's no way to navigate it sensibly. In this
308
+ case, manually resizing the element (desktop only - mobile doesn't
309
+ offer that) will force it to stay at the selected size even if more
310
+ content is added to it later.
311
+
312
+ - If we DO set a max-height then its growth is bounded but it also
313
+ cannot manually expanded by the user.
314
+
315
+ The lesser of the two evils seems to be to rely on the browser
316
+ feature that a manual resize of the element will pin its sits.
317
+*/
212318
}
213319
214
-body.chat #chat-input-line > button {
215
- max-width: 4em;
216
-}
217
-body.chat #chat-input-line > #chat-settings-button{
320
+body.chat #chat-input-line > #chat-button-settings{
218321
margin: 0 0 0 0.25em;
219322
max-width: 2em;
220323
}
221324
body.chat #chat-input-line > input[type=text],
222325
body.chat #chat-input-line > textarea {
223326
flex: 20 1 auto;
224327
max-width: revert;
225328
min-width: 20em;
226329
}
227
-body.chat #chat-input-line.single-line > input[type=text] {
330
+body.chat #chat-input-line.compact > input[type=text] {
228331
margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
229332
}
230333
/* Widget holding the file selection control and preview */
231334
body.chat #chat-input-file-area {
232335
display: flex;
233336
flex-direction: row;
234
- align-items: center;
235
- flex-wrap: wrap;
236
- margin: 0.25em 0 0 0 /* avoid nudging input area */;
337
+ margin: 0;
237338
}
238339
body.chat #chat-input-file-area > .file-selection-wrapper {
239340
align-self: flex-start;
240341
margin-right: 0.5em;
241342
flex: 0 1 auto;
242343
padding: 0.25em 0.5em;
243344
white-space: nowrap;
244345
}
245
-body.chat #chat-input-file-area .file-selection-wrapper > * {
246
- vertical-align: middle;
247
- margin: 0;
248
-}
249346
body.chat #chat-input-file {
250347
border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
251348
border-radius: 0.25em;
252349
padding: 0.25em;
253350
}
@@ -258,24 +355,25 @@
258355
body.chat #chat-input-file.dragover {
259356
border: 1px dashed green;
260357
}
261358
/* Widget holding the details of a selected/dropped file/image. */
262359
body.chat #chat-drop-details {
263
- flex: 0 1 auto;
264
- padding: 0.5em 1em;
265
- margin-left: 0.5em;
360
+ padding: 0 1em;
266361
white-space: pre;
267362
font-family: monospace;
363
+ margin: auto;
364
+ flex: 0;
268365
}
269366
270367
body.chat #chat-drop-details img {
271368
max-width: 45%;
272369
max-height: 45%;
273370
}
274371
body.chat .chat-view {
275372
flex: 20 1 auto
276
- /*ensure that these grow more than the non-.chat-view elements*/;
373
+ /*ensure that these grow more than the non-.chat-view elements.
374
+ Note that setting flex shrink to 0 breaks/disables scrolling!*/;
277375
margin-bottom: 0.2em;
278376
}
279377
body.chat #chat-config,
280378
body.chat #chat-preview {
281379
/* /chat configuration widget */
@@ -289,28 +387,41 @@
289387
}
290388
body.chat #chat-config #chat-config-options {
291389
/* /chat config options go here */
292390
flex: 1 1 auto;
293391
display: flex;
294
- flex-direction: column-reverse;
392
+ flex-direction: column;
295393
overflow: auto;
296394
}
297395
body.chat #chat-config #chat-config-options .menu-entry {
298396
display: flex;
299
- align-items: center;
397
+ align-items: baseline;
300398
flex-direction: row;
301399
flex-wrap: nowrap;
302400
padding: 1em;
303401
}
304
-body.chat #chat-config #chat-config-options .menu-entry > label {
402
+body.chat #chat-config #chat-config-options .menu-entry label[for] {
305403
cursor: pointer;
306404
}
307
-body.chat #chat-config #chat-config-options .menu-entry > input:first-child {
308
- margin-right: 1em;
405
+body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
406
+ min-width: 1.5rem;
407
+}
408
+body.chat #chat-config #chat-config-options .menu-entry span.hint {
409
+ /* Config menu hint text */
410
+ font-size: 80%;
411
+ white-space: pre-wrap;
412
+ display: inline-block;
413
+}
414
+body.chat #chat-config #chat-config-options .menu-entry:first-child {
415
+}
416
+body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
417
+ display: flex;
418
+ flex-direction: column;
419
+ align-self: baseline;
420
+ margin-left: 1em;
309421
}
310
-body.chat #chat-config #chat-config-options .menu-entry > label:first-child {
311
- margin-right: 0.5em;
422
+body.chat #chat-config #chat-config-options .menu-entry select {
312423
}
313424
body.chat #chat-preview #chat-preview-content {
314425
overflow: auto;
315426
flex: 1 1 auto;
316427
padding: 0.5em;
@@ -405,10 +516,16 @@
405516
}
406517
407518
body.chat #chat-clear-filter {
408519
margin: 0.25em 0.5em;
409520
}
521
+
522
+body.chat.fossil-dark-style #chat-button-attach > svg {
523
+ /* The black paperclip is barely visible in dark-mode
524
+ skins when they have dark buttons */
525
+ filter: invert(0.8);
526
+}
410527
411528
body.chat .anim-rotate-360 {
412529
animation: rotate-360 750ms linear;
413530
}
414531
@keyframes rotate-360 {
415532
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
10 display: flex;
11 flex-direction: column;
12 border: none;
13 align-items: flex-start;
14 }
 
 
 
 
15 body.chat .message-widget:last-of-type {
16 /* Latest message: reduce bottom gap */
17 margin-bottom: 0.1em;
18 }
19 body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
36 min-width: 9em /*avoid unsightly "underlap" with the neighboring
37 .message-widget-tab element*/;
38 white-space: normal;
39 }
40 body.chat.monospace-messages .message-widget-content,
41 body.chat.monospace-messages textarea,
42 body.chat.monospace-messages input[type=text]{
 
43 font-family: monospace;
44 }
45 body.chat .message-widget-content > * {
46 margin: 0;
47 padding: 0;
@@ -127,11 +132,11 @@
127 flex: 1 1 auto;
128 }
129 /* "Chat-only mode" hides the site header/footer, showing only
130 the chat app. */
131 body.chat.chat-only-mode{}
132 body.chat #chat-settings-button {}
133 /** Popup widget for the /chat settings. */
134 body.chat .chat-settings-popup {
135 font-size: 0.8em;
136 text-align: left;
137 display: flex;
@@ -168,86 +173,178 @@
168 body.chat #chat-input-area {
169 display: flex;
170 flex-direction: column;
171 padding: 0;
172 margin: 0;
173 position: initial /*sticky currently disabled due to scrolling-related issues*/;
174 /*bottom: 0;*/
175 }
176 body.chat:not(.chat-only-mode) #chat-input-area{
177 /* Safari user reports that 2em is necessary to keep the file selection
178 widget from overlapping the page footer, whereas a margin of 0 is fine
179 for FF/Chrome (and 2em is a *huge* waste of space for those). */
180 margin-bottom: 0;
181 }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183 /* Widget holding the chat message input field, send button, and
184 settings button. */
185 body.chat #chat-input-line {
186 display: flex;
187 flex-direction: row;
188 align-items: stretch;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189 }
190 body.chat #chat-input-line.single-line {
191 flex-wrap: wrap;
192 }
193 body.chat #chat-edit-buttons {
194 flex: 1 1 auto;
 
195 display: flex;
196 flex-direction: column;
197 justify-content: space-between;
198 }
199 body.chat #chat-input-line.single-line #chat-edit-buttons {
200 flex-direction: row;
 
 
201 }
202 body.chat #chat-edit-buttons > * {
 
203 flex: 1 1 auto;
204 padding: initial/*some skins mess this up for buttons*/;
205 }
206 body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
207 max-width: 4em;
208 margin: 0.25em;
209 }
210 body.chat #chat-input-line.single-line #chat-edit-buttons > * {
211 margin: 0 0.25em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212 }
213
214 body.chat #chat-input-line > button {
215 max-width: 4em;
216 }
217 body.chat #chat-input-line > #chat-settings-button{
218 margin: 0 0 0 0.25em;
219 max-width: 2em;
220 }
221 body.chat #chat-input-line > input[type=text],
222 body.chat #chat-input-line > textarea {
223 flex: 20 1 auto;
224 max-width: revert;
225 min-width: 20em;
226 }
227 body.chat #chat-input-line.single-line > input[type=text] {
228 margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
229 }
230 /* Widget holding the file selection control and preview */
231 body.chat #chat-input-file-area {
232 display: flex;
233 flex-direction: row;
234 align-items: center;
235 flex-wrap: wrap;
236 margin: 0.25em 0 0 0 /* avoid nudging input area */;
237 }
238 body.chat #chat-input-file-area > .file-selection-wrapper {
239 align-self: flex-start;
240 margin-right: 0.5em;
241 flex: 0 1 auto;
242 padding: 0.25em 0.5em;
243 white-space: nowrap;
244 }
245 body.chat #chat-input-file-area .file-selection-wrapper > * {
246 vertical-align: middle;
247 margin: 0;
248 }
249 body.chat #chat-input-file {
250 border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
251 border-radius: 0.25em;
252 padding: 0.25em;
253 }
@@ -258,24 +355,25 @@
258 body.chat #chat-input-file.dragover {
259 border: 1px dashed green;
260 }
261 /* Widget holding the details of a selected/dropped file/image. */
262 body.chat #chat-drop-details {
263 flex: 0 1 auto;
264 padding: 0.5em 1em;
265 margin-left: 0.5em;
266 white-space: pre;
267 font-family: monospace;
 
 
268 }
269
270 body.chat #chat-drop-details img {
271 max-width: 45%;
272 max-height: 45%;
273 }
274 body.chat .chat-view {
275 flex: 20 1 auto
276 /*ensure that these grow more than the non-.chat-view elements*/;
 
277 margin-bottom: 0.2em;
278 }
279 body.chat #chat-config,
280 body.chat #chat-preview {
281 /* /chat configuration widget */
@@ -289,28 +387,41 @@
289 }
290 body.chat #chat-config #chat-config-options {
291 /* /chat config options go here */
292 flex: 1 1 auto;
293 display: flex;
294 flex-direction: column-reverse;
295 overflow: auto;
296 }
297 body.chat #chat-config #chat-config-options .menu-entry {
298 display: flex;
299 align-items: center;
300 flex-direction: row;
301 flex-wrap: nowrap;
302 padding: 1em;
303 }
304 body.chat #chat-config #chat-config-options .menu-entry > label {
305 cursor: pointer;
306 }
307 body.chat #chat-config #chat-config-options .menu-entry > input:first-child {
308 margin-right: 1em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309 }
310 body.chat #chat-config #chat-config-options .menu-entry > label:first-child {
311 margin-right: 0.5em;
312 }
313 body.chat #chat-preview #chat-preview-content {
314 overflow: auto;
315 flex: 1 1 auto;
316 padding: 0.5em;
@@ -405,10 +516,16 @@
405 }
406
407 body.chat #chat-clear-filter {
408 margin: 0.25em 0.5em;
409 }
 
 
 
 
 
 
410
411 body.chat .anim-rotate-360 {
412 animation: rotate-360 750ms linear;
413 }
414 @keyframes rotate-360 {
415
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
10 display: flex;
11 flex-direction: column;
12 border: none;
13 align-items: flex-start;
14 }
15 body.chat button,
16 body.chat input[type=button] {
17 line-height: inherit/*undo skin-specific funkiness*/;
18 }
19 body.chat .message-widget:last-of-type {
20 /* Latest message: reduce bottom gap */
21 margin-bottom: 0.1em;
22 }
23 body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
40 min-width: 9em /*avoid unsightly "underlap" with the neighboring
41 .message-widget-tab element*/;
42 white-space: normal;
43 }
44 body.chat.monospace-messages .message-widget-content,
45 /*body.chat.monospace-messages textarea,*/
46 /*body.chat.monospace-messages input[type=text],*/
47 body.chat.monospace-messages #chat-input-field{
48 font-family: monospace;
49 }
50 body.chat .message-widget-content > * {
51 margin: 0;
52 padding: 0;
@@ -127,11 +132,11 @@
132 flex: 1 1 auto;
133 }
134 /* "Chat-only mode" hides the site header/footer, showing only
135 the chat app. */
136 body.chat.chat-only-mode{}
137 body.chat #chat-button-settings {}
138 /** Popup widget for the /chat settings. */
139 body.chat .chat-settings-popup {
140 font-size: 0.8em;
141 text-align: left;
142 display: flex;
@@ -168,86 +173,178 @@
173 body.chat #chat-input-area {
174 display: flex;
175 flex-direction: column;
176 padding: 0;
177 margin: 0;
178 flex: 0 1 auto;
 
179 }
180 body.chat:not(.chat-only-mode) #chat-input-area{
181 /* Safari user reports that 2em is necessary to keep the file selection
182 widget from overlapping the page footer, whereas a margin of 0 is fine
183 for FF/Chrome (and 2em is a *huge* waste of space for those). */
184 margin-bottom: 0;
185 }
186 #chat-input-field {
187 display: inline-block/*supposed workaround for Chrome weirdness*/;
188 padding: 0.2em;
189 flex: 10 1 auto;
190 background-color: rgba(156,156,156,0.3);
191 overflow: auto;
192 resize: vertical;
193 }
194 #chat-input-field:empty::before {
195 content: attr(data-placeholder);
196 opacity: 0.6;
197 }
198 #chat-input-field:not(:focus){
199 border-width: 1px;
200 border-style: solid;
201 border-radius: 0.25em;
202 }
203 #chat-input-field:focus{
204 /* This transparent border helps avoid the text shifting around
205 when the contenteditable attribute causes a border (which we
206 apparently cannot style) to be added. */
207 border-width: 1px;
208 border-style: solid;
209 border-color: transparent;
210 border-radius: 0.25em;
211 }
212 /* Widget holding the chat message input field, send button, and
213 settings button. */
214 body.chat #chat-input-line {
215 display: flex;
216 flex-direction: row;
217 align-items: stretch;
218 flex-wrap: nowrap;
219 }
220 /*body.chat #chat-input-line:not(.compact) {
221 flex-wrap: nowrap;
222 }*/
223 body.chat #chat-input-line.compact {
224 /* "The problem" with wrapping, together with a contenteditable input
225 field, is that the latter grows as the user types, so causes
226 wrapping to happen while they type, then to unwrap as soon as the
227 input field is cleared (when the message is sent). When we stay
228 wrapped in compact mode, the wrapped buttons simply take up too
229 much space. */
230 /*flex-wrap: wrap;
231 justify-content: flex-end;*/
232 flex-direction: column;
233 /**
234 We "really do" need column orientation here because it's the
235 only way to eliminate the possibility that (A) the buttons
236 get truncated in very narrow windows and (B) that they keep
237 stable positions.
238 */
239 }
240 body.chat #chat-input-line.compact #chat-input-field {
 
241 }
242
243 body.chat #chat-buttons-wrapper {
244 flex: 0 1 auto;
245 display: flex;
246 flex-direction: column;
247 align-items: center;
248 min-width: 4em;
249 min-height: 1.5em;
250 align-self: flex-end
251 /*keep buttons stable at bottom/right even when input field
252 resizes */;
253 }
254 body.chat #chat-input-line.compact #chat-buttons-wrapper {
255 flex-direction: row;
256 flex: 1 1 auto;
257 align-self: stretch;
258 justify-content: flex-end;
259 /*flex-wrap: wrap;*/
260 /* Wrapping would be ideal except that the edit widget
261 grows in width as the user types, moving the buttons
262 around */
263 }
264 body.chat #chat-buttons-wrapper > .cbutton {
265 padding: 0;
266 display: inline-block;
267 border-width: 1px;
268 border-style: solid;
269 border-radius: 0.25em;
270 min-width: 4ex;
271 max-width: 4ex;
272 min-height: 4ex;
273 max-height: 4ex;
274 margin: 0.125em;
275 display: inline-flex;
276 justify-content: center;
277 align-items: center;
278 cursor: pointer;
279 font-size: 130%;
280 }
281 body.chat #chat-buttons-wrapper > .cbutton:hover {
282 background-color: rgba(200,200,200,0.3);
283 }
284 body.chat #chat-input-line.compact #chat-buttons-wrapper > .cbutton {
285 margin: 2px 0.125em 0 0.125em;
286 min-width: 6ex;
287 max-width: 6ex;
288 min-height: 2.3ex;
289 max-height: 2.3ex;
290 font-size: 120%;
291 }
292 body.chat #chat-input-line.compact #chat-buttons-wrapper #chat-button-submit {
293 min-width: 12ex;
294 }
295 body.chat #chat-input-line:not(.compact) #chat-input-field {
296 /*border-left-style: double;
297 border-left-width: 3px;
298 border-right-style: double;
299 border-right-width: 3px;*/
300 min-height: 4rem;
301 /*max-height: 50rem;*/
302 /*
303 Problems related to max-height:
304
305 - If we do NOT set a max-height then pasting/typing a large amount
306 of text can cause this element to grow without bounds, larger than
307 the window, and there's no way to navigate it sensibly. In this
308 case, manually resizing the element (desktop only - mobile doesn't
309 offer that) will force it to stay at the selected size even if more
310 content is added to it later.
311
312 - If we DO set a max-height then its growth is bounded but it also
313 cannot manually expanded by the user.
314
315 The lesser of the two evils seems to be to rely on the browser
316 feature that a manual resize of the element will pin its sits.
317 */
318 }
319
320 body.chat #chat-input-line > #chat-button-settings{
 
 
 
321 margin: 0 0 0 0.25em;
322 max-width: 2em;
323 }
324 body.chat #chat-input-line > input[type=text],
325 body.chat #chat-input-line > textarea {
326 flex: 20 1 auto;
327 max-width: revert;
328 min-width: 20em;
329 }
330 body.chat #chat-input-line.compact > input[type=text] {
331 margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
332 }
333 /* Widget holding the file selection control and preview */
334 body.chat #chat-input-file-area {
335 display: flex;
336 flex-direction: row;
337 margin: 0;
 
 
338 }
339 body.chat #chat-input-file-area > .file-selection-wrapper {
340 align-self: flex-start;
341 margin-right: 0.5em;
342 flex: 0 1 auto;
343 padding: 0.25em 0.5em;
344 white-space: nowrap;
345 }
 
 
 
 
346 body.chat #chat-input-file {
347 border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
348 border-radius: 0.25em;
349 padding: 0.25em;
350 }
@@ -258,24 +355,25 @@
355 body.chat #chat-input-file.dragover {
356 border: 1px dashed green;
357 }
358 /* Widget holding the details of a selected/dropped file/image. */
359 body.chat #chat-drop-details {
360 padding: 0 1em;
 
 
361 white-space: pre;
362 font-family: monospace;
363 margin: auto;
364 flex: 0;
365 }
366
367 body.chat #chat-drop-details img {
368 max-width: 45%;
369 max-height: 45%;
370 }
371 body.chat .chat-view {
372 flex: 20 1 auto
373 /*ensure that these grow more than the non-.chat-view elements.
374 Note that setting flex shrink to 0 breaks/disables scrolling!*/;
375 margin-bottom: 0.2em;
376 }
377 body.chat #chat-config,
378 body.chat #chat-preview {
379 /* /chat configuration widget */
@@ -289,28 +387,41 @@
387 }
388 body.chat #chat-config #chat-config-options {
389 /* /chat config options go here */
390 flex: 1 1 auto;
391 display: flex;
392 flex-direction: column;
393 overflow: auto;
394 }
395 body.chat #chat-config #chat-config-options .menu-entry {
396 display: flex;
397 align-items: baseline;
398 flex-direction: row;
399 flex-wrap: nowrap;
400 padding: 1em;
401 }
402 body.chat #chat-config #chat-config-options .menu-entry label[for] {
403 cursor: pointer;
404 }
405 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
406 min-width: 1.5rem;
407 }
408 body.chat #chat-config #chat-config-options .menu-entry span.hint {
409 /* Config menu hint text */
410 font-size: 80%;
411 white-space: pre-wrap;
412 display: inline-block;
413 }
414 body.chat #chat-config #chat-config-options .menu-entry:first-child {
415 }
416 body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
417 display: flex;
418 flex-direction: column;
419 align-self: baseline;
420 margin-left: 1em;
421 }
422 body.chat #chat-config #chat-config-options .menu-entry select {
 
423 }
424 body.chat #chat-preview #chat-preview-content {
425 overflow: auto;
426 flex: 1 1 auto;
427 padding: 0.5em;
@@ -405,10 +516,16 @@
516 }
517
518 body.chat #chat-clear-filter {
519 margin: 0.25em 0.5em;
520 }
521
522 body.chat.fossil-dark-style #chat-button-attach > svg {
523 /* The black paperclip is barely visible in dark-mode
524 skins when they have dark buttons */
525 filter: invert(0.8);
526 }
527
528 body.chat .anim-rotate-360 {
529 animation: rotate-360 750ms linear;
530 }
531 @keyframes rotate-360 {
532
+163 -46
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
1010
display: flex;
1111
flex-direction: column;
1212
border: none;
1313
align-items: flex-start;
1414
}
15
+body.chat button,
16
+body.chat input[type=button] {
17
+ line-height: inherit/*undo skin-specific funkiness*/;
18
+}
1519
body.chat .message-widget:last-of-type {
1620
/* Latest message: reduce bottom gap */
1721
margin-bottom: 0.1em;
1822
}
1923
body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
3640
min-width: 9em /*avoid unsightly "underlap" with the neighboring
3741
.message-widget-tab element*/;
3842
white-space: normal;
3943
}
4044
body.chat.monospace-messages .message-widget-content,
41
-body.chat.monospace-messages textarea,
42
-body.chat.monospace-messages input[type=text]{
45
+/*body.chat.monospace-messages textarea,*/
46
+/*body.chat.monospace-messages input[type=text],*/
47
+body.chat.monospace-messages #chat-input-field{
4348
font-family: monospace;
4449
}
4550
body.chat .message-widget-content > * {
4651
margin: 0;
4752
padding: 0;
@@ -127,11 +132,11 @@
127132
flex: 1 1 auto;
128133
}
129134
/* "Chat-only mode" hides the site header/footer, showing only
130135
the chat app. */
131136
body.chat.chat-only-mode{}
132
-body.chat #chat-settings-button {}
137
+body.chat #chat-button-settings {}
133138
/** Popup widget for the /chat settings. */
134139
body.chat .chat-settings-popup {
135140
font-size: 0.8em;
136141
text-align: left;
137142
display: flex;
@@ -168,86 +173,178 @@
168173
body.chat #chat-input-area {
169174
display: flex;
170175
flex-direction: column;
171176
padding: 0;
172177
margin: 0;
173
- position: initial /*sticky currently disabled due to scrolling-related issues*/;
174
- /*bottom: 0;*/
178
+ flex: 0 1 auto;
175179
}
176180
body.chat:not(.chat-only-mode) #chat-input-area{
177181
/* Safari user reports that 2em is necessary to keep the file selection
178182
widget from overlapping the page footer, whereas a margin of 0 is fine
179183
for FF/Chrome (and 2em is a *huge* waste of space for those). */
180184
margin-bottom: 0;
181185
}
182
-
186
+#chat-input-field {
187
+ display: inline-block/*supposed workaround for Chrome weirdness*/;
188
+ padding: 0.2em;
189
+ flex: 10 1 auto;
190
+ background-color: rgba(156,156,156,0.3);
191
+ overflow: auto;
192
+ resize: vertical;
193
+}
194
+#chat-input-field:empty::before {
195
+ content: attr(data-placeholder);
196
+ opacity: 0.6;
197
+}
198
+#chat-input-field:not(:focus){
199
+ border-width: 1px;
200
+ border-style: solid;
201
+ border-radius: 0.25em;
202
+}
203
+#chat-input-field:focus{
204
+ /* This transparent border helps avoid the text shifting around
205
+ when the contenteditable attribute causes a border (which we
206
+ apparently cannot style) to be added. */
207
+ border-width: 1px;
208
+ border-style: solid;
209
+ border-color: transparent;
210
+ border-radius: 0.25em;
211
+}
183212
/* Widget holding the chat message input field, send button, and
184213
settings button. */
185214
body.chat #chat-input-line {
186215
display: flex;
187216
flex-direction: row;
188217
align-items: stretch;
218
+ flex-wrap: nowrap;
219
+}
220
+/*body.chat #chat-input-line:not(.compact) {
221
+ flex-wrap: nowrap;
222
+}*/
223
+body.chat #chat-input-line.compact {
224
+ /* "The problem" with wrapping, together with a contenteditable input
225
+ field, is that the latter grows as the user types, so causes
226
+ wrapping to happen while they type, then to unwrap as soon as the
227
+ input field is cleared (when the message is sent). When we stay
228
+ wrapped in compact mode, the wrapped buttons simply take up too
229
+ much space. */
230
+ /*flex-wrap: wrap;
231
+ justify-content: flex-end;*/
232
+ flex-direction: column;
233
+ /**
234
+ We "really do" need column orientation here because it's the
235
+ only way to eliminate the possibility that (A) the buttons
236
+ get truncated in very narrow windows and (B) that they keep
237
+ stable positions.
238
+ */
189239
}
190
-body.chat #chat-input-line.single-line {
191
- flex-wrap: wrap;
240
+body.chat #chat-input-line.compact #chat-input-field {
192241
}
193
-body.chat #chat-edit-buttons {
194
- flex: 1 1 auto;
242
+
243
+body.chat #chat-buttons-wrapper {
244
+ flex: 0 1 auto;
195245
display: flex;
196246
flex-direction: column;
197
- justify-content: space-between;
198
-}
199
-body.chat #chat-input-line.single-line #chat-edit-buttons {
200
- flex-direction: row;
247
+ align-items: center;
248
+ min-width: 4em;
249
+ min-height: 1.5em;
250
+ align-self: flex-end
251
+ /*keep buttons stable at bottom/right even when input field
252
+ resizes */;
201253
}
202
-body.chat #chat-edit-buttons > * {
254
+body.chat #chat-input-line.compact #chat-buttons-wrapper {
255
+ flex-direction: row;
203256
flex: 1 1 auto;
204
- padding: initial/*some skins mess this up for buttons*/;
205
-}
206
-body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
207
- max-width: 4em;
208
- margin: 0.25em;
209
-}
210
-body.chat #chat-input-line.single-line #chat-edit-buttons > * {
211
- margin: 0 0.25em;
257
+ align-self: stretch;
258
+ justify-content: flex-end;
259
+ /*flex-wrap: wrap;*/
260
+ /* Wrapping would be ideal except that the edit widget
261
+ grows in width as the user types, moving the buttons
262
+ around */
263
+}
264
+body.chat #chat-buttons-wrapper > .cbutton {
265
+ padding: 0;
266
+ display: inline-block;
267
+ border-width: 1px;
268
+ border-style: solid;
269
+ border-radius: 0.25em;
270
+ min-width: 4ex;
271
+ max-width: 4ex;
272
+ min-height: 4ex;
273
+ max-height: 4ex;
274
+ margin: 0.125em;
275
+ display: inline-flex;
276
+ justify-content: center;
277
+ align-items: center;
278
+ cursor: pointer;
279
+ font-size: 130%;
280
+}
281
+body.chat #chat-buttons-wrapper > .cbutton:hover {
282
+ background-color: rgba(200,200,200,0.3);
283
+}
284
+body.chat #chat-input-line.compact #chat-buttons-wrapper > .cbutton {
285
+ margin: 2px 0.125em 0 0.125em;
286
+ min-width: 6ex;
287
+ max-width: 6ex;
288
+ min-height: 2.3ex;
289
+ max-height: 2.3ex;
290
+ font-size: 120%;
291
+}
292
+body.chat #chat-input-line.compact #chat-buttons-wrapper #chat-button-submit {
293
+ min-width: 12ex;
294
+}
295
+body.chat #chat-input-line:not(.compact) #chat-input-field {
296
+ /*border-left-style: double;
297
+ border-left-width: 3px;
298
+ border-right-style: double;
299
+ border-right-width: 3px;*/
300
+ min-height: 4rem;
301
+ /*max-height: 50rem;*/
302
+/*
303
+ Problems related to max-height:
304
+
305
+ - If we do NOT set a max-height then pasting/typing a large amount
306
+ of text can cause this element to grow without bounds, larger than
307
+ the window, and there's no way to navigate it sensibly. In this
308
+ case, manually resizing the element (desktop only - mobile doesn't
309
+ offer that) will force it to stay at the selected size even if more
310
+ content is added to it later.
311
+
312
+ - If we DO set a max-height then its growth is bounded but it also
313
+ cannot manually expanded by the user.
314
+
315
+ The lesser of the two evils seems to be to rely on the browser
316
+ feature that a manual resize of the element will pin its sits.
317
+*/
212318
}
213319
214
-body.chat #chat-input-line > button {
215
- max-width: 4em;
216
-}
217
-body.chat #chat-input-line > #chat-settings-button{
320
+body.chat #chat-input-line > #chat-button-settings{
218321
margin: 0 0 0 0.25em;
219322
max-width: 2em;
220323
}
221324
body.chat #chat-input-line > input[type=text],
222325
body.chat #chat-input-line > textarea {
223326
flex: 20 1 auto;
224327
max-width: revert;
225328
min-width: 20em;
226329
}
227
-body.chat #chat-input-line.single-line > input[type=text] {
330
+body.chat #chat-input-line.compact > input[type=text] {
228331
margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
229332
}
230333
/* Widget holding the file selection control and preview */
231334
body.chat #chat-input-file-area {
232335
display: flex;
233336
flex-direction: row;
234
- align-items: center;
235
- flex-wrap: wrap;
236
- margin: 0.25em 0 0 0 /* avoid nudging input area */;
337
+ margin: 0;
237338
}
238339
body.chat #chat-input-file-area > .file-selection-wrapper {
239340
align-self: flex-start;
240341
margin-right: 0.5em;
241342
flex: 0 1 auto;
242343
padding: 0.25em 0.5em;
243344
white-space: nowrap;
244345
}
245
-body.chat #chat-input-file-area .file-selection-wrapper > * {
246
- vertical-align: middle;
247
- margin: 0;
248
-}
249346
body.chat #chat-input-file {
250347
border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
251348
border-radius: 0.25em;
252349
padding: 0.25em;
253350
}
@@ -258,24 +355,25 @@
258355
body.chat #chat-input-file.dragover {
259356
border: 1px dashed green;
260357
}
261358
/* Widget holding the details of a selected/dropped file/image. */
262359
body.chat #chat-drop-details {
263
- flex: 0 1 auto;
264
- padding: 0.5em 1em;
265
- margin-left: 0.5em;
360
+ padding: 0 1em;
266361
white-space: pre;
267362
font-family: monospace;
363
+ margin: auto;
364
+ flex: 0;
268365
}
269366
270367
body.chat #chat-drop-details img {
271368
max-width: 45%;
272369
max-height: 45%;
273370
}
274371
body.chat .chat-view {
275372
flex: 20 1 auto
276
- /*ensure that these grow more than the non-.chat-view elements*/;
373
+ /*ensure that these grow more than the non-.chat-view elements.
374
+ Note that setting flex shrink to 0 breaks/disables scrolling!*/;
277375
margin-bottom: 0.2em;
278376
}
279377
body.chat #chat-config,
280378
body.chat #chat-preview {
281379
/* /chat configuration widget */
@@ -289,28 +387,41 @@
289387
}
290388
body.chat #chat-config #chat-config-options {
291389
/* /chat config options go here */
292390
flex: 1 1 auto;
293391
display: flex;
294
- flex-direction: column-reverse;
392
+ flex-direction: column;
295393
overflow: auto;
296394
}
297395
body.chat #chat-config #chat-config-options .menu-entry {
298396
display: flex;
299
- align-items: center;
397
+ align-items: baseline;
300398
flex-direction: row;
301399
flex-wrap: nowrap;
302400
padding: 1em;
303401
}
304
-body.chat #chat-config #chat-config-options .menu-entry > label {
402
+body.chat #chat-config #chat-config-options .menu-entry label[for] {
305403
cursor: pointer;
306404
}
307
-body.chat #chat-config #chat-config-options .menu-entry > input:first-child {
308
- margin-right: 1em;
405
+body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
406
+ min-width: 1.5rem;
407
+}
408
+body.chat #chat-config #chat-config-options .menu-entry span.hint {
409
+ /* Config menu hint text */
410
+ font-size: 80%;
411
+ white-space: pre-wrap;
412
+ display: inline-block;
413
+}
414
+body.chat #chat-config #chat-config-options .menu-entry:first-child {
415
+}
416
+body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
417
+ display: flex;
418
+ flex-direction: column;
419
+ align-self: baseline;
420
+ margin-left: 1em;
309421
}
310
-body.chat #chat-config #chat-config-options .menu-entry > label:first-child {
311
- margin-right: 0.5em;
422
+body.chat #chat-config #chat-config-options .menu-entry select {
312423
}
313424
body.chat #chat-preview #chat-preview-content {
314425
overflow: auto;
315426
flex: 1 1 auto;
316427
padding: 0.5em;
@@ -405,10 +516,16 @@
405516
}
406517
407518
body.chat #chat-clear-filter {
408519
margin: 0.25em 0.5em;
409520
}
521
+
522
+body.chat.fossil-dark-style #chat-button-attach > svg {
523
+ /* The black paperclip is barely visible in dark-mode
524
+ skins when they have dark buttons */
525
+ filter: invert(0.8);
526
+}
410527
411528
body.chat .anim-rotate-360 {
412529
animation: rotate-360 750ms linear;
413530
}
414531
@keyframes rotate-360 {
415532
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
10 display: flex;
11 flex-direction: column;
12 border: none;
13 align-items: flex-start;
14 }
 
 
 
 
15 body.chat .message-widget:last-of-type {
16 /* Latest message: reduce bottom gap */
17 margin-bottom: 0.1em;
18 }
19 body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
36 min-width: 9em /*avoid unsightly "underlap" with the neighboring
37 .message-widget-tab element*/;
38 white-space: normal;
39 }
40 body.chat.monospace-messages .message-widget-content,
41 body.chat.monospace-messages textarea,
42 body.chat.monospace-messages input[type=text]{
 
43 font-family: monospace;
44 }
45 body.chat .message-widget-content > * {
46 margin: 0;
47 padding: 0;
@@ -127,11 +132,11 @@
127 flex: 1 1 auto;
128 }
129 /* "Chat-only mode" hides the site header/footer, showing only
130 the chat app. */
131 body.chat.chat-only-mode{}
132 body.chat #chat-settings-button {}
133 /** Popup widget for the /chat settings. */
134 body.chat .chat-settings-popup {
135 font-size: 0.8em;
136 text-align: left;
137 display: flex;
@@ -168,86 +173,178 @@
168 body.chat #chat-input-area {
169 display: flex;
170 flex-direction: column;
171 padding: 0;
172 margin: 0;
173 position: initial /*sticky currently disabled due to scrolling-related issues*/;
174 /*bottom: 0;*/
175 }
176 body.chat:not(.chat-only-mode) #chat-input-area{
177 /* Safari user reports that 2em is necessary to keep the file selection
178 widget from overlapping the page footer, whereas a margin of 0 is fine
179 for FF/Chrome (and 2em is a *huge* waste of space for those). */
180 margin-bottom: 0;
181 }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183 /* Widget holding the chat message input field, send button, and
184 settings button. */
185 body.chat #chat-input-line {
186 display: flex;
187 flex-direction: row;
188 align-items: stretch;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189 }
190 body.chat #chat-input-line.single-line {
191 flex-wrap: wrap;
192 }
193 body.chat #chat-edit-buttons {
194 flex: 1 1 auto;
 
195 display: flex;
196 flex-direction: column;
197 justify-content: space-between;
198 }
199 body.chat #chat-input-line.single-line #chat-edit-buttons {
200 flex-direction: row;
 
 
201 }
202 body.chat #chat-edit-buttons > * {
 
203 flex: 1 1 auto;
204 padding: initial/*some skins mess this up for buttons*/;
205 }
206 body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
207 max-width: 4em;
208 margin: 0.25em;
209 }
210 body.chat #chat-input-line.single-line #chat-edit-buttons > * {
211 margin: 0 0.25em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212 }
213
214 body.chat #chat-input-line > button {
215 max-width: 4em;
216 }
217 body.chat #chat-input-line > #chat-settings-button{
218 margin: 0 0 0 0.25em;
219 max-width: 2em;
220 }
221 body.chat #chat-input-line > input[type=text],
222 body.chat #chat-input-line > textarea {
223 flex: 20 1 auto;
224 max-width: revert;
225 min-width: 20em;
226 }
227 body.chat #chat-input-line.single-line > input[type=text] {
228 margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
229 }
230 /* Widget holding the file selection control and preview */
231 body.chat #chat-input-file-area {
232 display: flex;
233 flex-direction: row;
234 align-items: center;
235 flex-wrap: wrap;
236 margin: 0.25em 0 0 0 /* avoid nudging input area */;
237 }
238 body.chat #chat-input-file-area > .file-selection-wrapper {
239 align-self: flex-start;
240 margin-right: 0.5em;
241 flex: 0 1 auto;
242 padding: 0.25em 0.5em;
243 white-space: nowrap;
244 }
245 body.chat #chat-input-file-area .file-selection-wrapper > * {
246 vertical-align: middle;
247 margin: 0;
248 }
249 body.chat #chat-input-file {
250 border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
251 border-radius: 0.25em;
252 padding: 0.25em;
253 }
@@ -258,24 +355,25 @@
258 body.chat #chat-input-file.dragover {
259 border: 1px dashed green;
260 }
261 /* Widget holding the details of a selected/dropped file/image. */
262 body.chat #chat-drop-details {
263 flex: 0 1 auto;
264 padding: 0.5em 1em;
265 margin-left: 0.5em;
266 white-space: pre;
267 font-family: monospace;
 
 
268 }
269
270 body.chat #chat-drop-details img {
271 max-width: 45%;
272 max-height: 45%;
273 }
274 body.chat .chat-view {
275 flex: 20 1 auto
276 /*ensure that these grow more than the non-.chat-view elements*/;
 
277 margin-bottom: 0.2em;
278 }
279 body.chat #chat-config,
280 body.chat #chat-preview {
281 /* /chat configuration widget */
@@ -289,28 +387,41 @@
289 }
290 body.chat #chat-config #chat-config-options {
291 /* /chat config options go here */
292 flex: 1 1 auto;
293 display: flex;
294 flex-direction: column-reverse;
295 overflow: auto;
296 }
297 body.chat #chat-config #chat-config-options .menu-entry {
298 display: flex;
299 align-items: center;
300 flex-direction: row;
301 flex-wrap: nowrap;
302 padding: 1em;
303 }
304 body.chat #chat-config #chat-config-options .menu-entry > label {
305 cursor: pointer;
306 }
307 body.chat #chat-config #chat-config-options .menu-entry > input:first-child {
308 margin-right: 1em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309 }
310 body.chat #chat-config #chat-config-options .menu-entry > label:first-child {
311 margin-right: 0.5em;
312 }
313 body.chat #chat-preview #chat-preview-content {
314 overflow: auto;
315 flex: 1 1 auto;
316 padding: 0.5em;
@@ -405,10 +516,16 @@
405 }
406
407 body.chat #chat-clear-filter {
408 margin: 0.25em 0.5em;
409 }
 
 
 
 
 
 
410
411 body.chat .anim-rotate-360 {
412 animation: rotate-360 750ms linear;
413 }
414 @keyframes rotate-360 {
415
--- src/style.chat.css
+++ src/style.chat.css
@@ -10,10 +10,14 @@
10 display: flex;
11 flex-direction: column;
12 border: none;
13 align-items: flex-start;
14 }
15 body.chat button,
16 body.chat input[type=button] {
17 line-height: inherit/*undo skin-specific funkiness*/;
18 }
19 body.chat .message-widget:last-of-type {
20 /* Latest message: reduce bottom gap */
21 margin-bottom: 0.1em;
22 }
23 body.chat.my-messages-right .message-widget.mine {
@@ -36,12 +40,13 @@
40 min-width: 9em /*avoid unsightly "underlap" with the neighboring
41 .message-widget-tab element*/;
42 white-space: normal;
43 }
44 body.chat.monospace-messages .message-widget-content,
45 /*body.chat.monospace-messages textarea,*/
46 /*body.chat.monospace-messages input[type=text],*/
47 body.chat.monospace-messages #chat-input-field{
48 font-family: monospace;
49 }
50 body.chat .message-widget-content > * {
51 margin: 0;
52 padding: 0;
@@ -127,11 +132,11 @@
132 flex: 1 1 auto;
133 }
134 /* "Chat-only mode" hides the site header/footer, showing only
135 the chat app. */
136 body.chat.chat-only-mode{}
137 body.chat #chat-button-settings {}
138 /** Popup widget for the /chat settings. */
139 body.chat .chat-settings-popup {
140 font-size: 0.8em;
141 text-align: left;
142 display: flex;
@@ -168,86 +173,178 @@
173 body.chat #chat-input-area {
174 display: flex;
175 flex-direction: column;
176 padding: 0;
177 margin: 0;
178 flex: 0 1 auto;
 
179 }
180 body.chat:not(.chat-only-mode) #chat-input-area{
181 /* Safari user reports that 2em is necessary to keep the file selection
182 widget from overlapping the page footer, whereas a margin of 0 is fine
183 for FF/Chrome (and 2em is a *huge* waste of space for those). */
184 margin-bottom: 0;
185 }
186 #chat-input-field {
187 display: inline-block/*supposed workaround for Chrome weirdness*/;
188 padding: 0.2em;
189 flex: 10 1 auto;
190 background-color: rgba(156,156,156,0.3);
191 overflow: auto;
192 resize: vertical;
193 }
194 #chat-input-field:empty::before {
195 content: attr(data-placeholder);
196 opacity: 0.6;
197 }
198 #chat-input-field:not(:focus){
199 border-width: 1px;
200 border-style: solid;
201 border-radius: 0.25em;
202 }
203 #chat-input-field:focus{
204 /* This transparent border helps avoid the text shifting around
205 when the contenteditable attribute causes a border (which we
206 apparently cannot style) to be added. */
207 border-width: 1px;
208 border-style: solid;
209 border-color: transparent;
210 border-radius: 0.25em;
211 }
212 /* Widget holding the chat message input field, send button, and
213 settings button. */
214 body.chat #chat-input-line {
215 display: flex;
216 flex-direction: row;
217 align-items: stretch;
218 flex-wrap: nowrap;
219 }
220 /*body.chat #chat-input-line:not(.compact) {
221 flex-wrap: nowrap;
222 }*/
223 body.chat #chat-input-line.compact {
224 /* "The problem" with wrapping, together with a contenteditable input
225 field, is that the latter grows as the user types, so causes
226 wrapping to happen while they type, then to unwrap as soon as the
227 input field is cleared (when the message is sent). When we stay
228 wrapped in compact mode, the wrapped buttons simply take up too
229 much space. */
230 /*flex-wrap: wrap;
231 justify-content: flex-end;*/
232 flex-direction: column;
233 /**
234 We "really do" need column orientation here because it's the
235 only way to eliminate the possibility that (A) the buttons
236 get truncated in very narrow windows and (B) that they keep
237 stable positions.
238 */
239 }
240 body.chat #chat-input-line.compact #chat-input-field {
 
241 }
242
243 body.chat #chat-buttons-wrapper {
244 flex: 0 1 auto;
245 display: flex;
246 flex-direction: column;
247 align-items: center;
248 min-width: 4em;
249 min-height: 1.5em;
250 align-self: flex-end
251 /*keep buttons stable at bottom/right even when input field
252 resizes */;
253 }
254 body.chat #chat-input-line.compact #chat-buttons-wrapper {
255 flex-direction: row;
256 flex: 1 1 auto;
257 align-self: stretch;
258 justify-content: flex-end;
259 /*flex-wrap: wrap;*/
260 /* Wrapping would be ideal except that the edit widget
261 grows in width as the user types, moving the buttons
262 around */
263 }
264 body.chat #chat-buttons-wrapper > .cbutton {
265 padding: 0;
266 display: inline-block;
267 border-width: 1px;
268 border-style: solid;
269 border-radius: 0.25em;
270 min-width: 4ex;
271 max-width: 4ex;
272 min-height: 4ex;
273 max-height: 4ex;
274 margin: 0.125em;
275 display: inline-flex;
276 justify-content: center;
277 align-items: center;
278 cursor: pointer;
279 font-size: 130%;
280 }
281 body.chat #chat-buttons-wrapper > .cbutton:hover {
282 background-color: rgba(200,200,200,0.3);
283 }
284 body.chat #chat-input-line.compact #chat-buttons-wrapper > .cbutton {
285 margin: 2px 0.125em 0 0.125em;
286 min-width: 6ex;
287 max-width: 6ex;
288 min-height: 2.3ex;
289 max-height: 2.3ex;
290 font-size: 120%;
291 }
292 body.chat #chat-input-line.compact #chat-buttons-wrapper #chat-button-submit {
293 min-width: 12ex;
294 }
295 body.chat #chat-input-line:not(.compact) #chat-input-field {
296 /*border-left-style: double;
297 border-left-width: 3px;
298 border-right-style: double;
299 border-right-width: 3px;*/
300 min-height: 4rem;
301 /*max-height: 50rem;*/
302 /*
303 Problems related to max-height:
304
305 - If we do NOT set a max-height then pasting/typing a large amount
306 of text can cause this element to grow without bounds, larger than
307 the window, and there's no way to navigate it sensibly. In this
308 case, manually resizing the element (desktop only - mobile doesn't
309 offer that) will force it to stay at the selected size even if more
310 content is added to it later.
311
312 - If we DO set a max-height then its growth is bounded but it also
313 cannot manually expanded by the user.
314
315 The lesser of the two evils seems to be to rely on the browser
316 feature that a manual resize of the element will pin its sits.
317 */
318 }
319
320 body.chat #chat-input-line > #chat-button-settings{
 
 
 
321 margin: 0 0 0 0.25em;
322 max-width: 2em;
323 }
324 body.chat #chat-input-line > input[type=text],
325 body.chat #chat-input-line > textarea {
326 flex: 20 1 auto;
327 max-width: revert;
328 min-width: 20em;
329 }
330 body.chat #chat-input-line.compact > input[type=text] {
331 margin: 0 0 0.25em 0/* gap for if/when buttons wrap*/;
332 }
333 /* Widget holding the file selection control and preview */
334 body.chat #chat-input-file-area {
335 display: flex;
336 flex-direction: row;
337 margin: 0;
 
 
338 }
339 body.chat #chat-input-file-area > .file-selection-wrapper {
340 align-self: flex-start;
341 margin-right: 0.5em;
342 flex: 0 1 auto;
343 padding: 0.25em 0.5em;
344 white-space: nowrap;
345 }
 
 
 
 
346 body.chat #chat-input-file {
347 border:1px solid rgba(0,0,0,0);/*avoid UI shift during drop-targeting*/
348 border-radius: 0.25em;
349 padding: 0.25em;
350 }
@@ -258,24 +355,25 @@
355 body.chat #chat-input-file.dragover {
356 border: 1px dashed green;
357 }
358 /* Widget holding the details of a selected/dropped file/image. */
359 body.chat #chat-drop-details {
360 padding: 0 1em;
 
 
361 white-space: pre;
362 font-family: monospace;
363 margin: auto;
364 flex: 0;
365 }
366
367 body.chat #chat-drop-details img {
368 max-width: 45%;
369 max-height: 45%;
370 }
371 body.chat .chat-view {
372 flex: 20 1 auto
373 /*ensure that these grow more than the non-.chat-view elements.
374 Note that setting flex shrink to 0 breaks/disables scrolling!*/;
375 margin-bottom: 0.2em;
376 }
377 body.chat #chat-config,
378 body.chat #chat-preview {
379 /* /chat configuration widget */
@@ -289,28 +387,41 @@
387 }
388 body.chat #chat-config #chat-config-options {
389 /* /chat config options go here */
390 flex: 1 1 auto;
391 display: flex;
392 flex-direction: column;
393 overflow: auto;
394 }
395 body.chat #chat-config #chat-config-options .menu-entry {
396 display: flex;
397 align-items: baseline;
398 flex-direction: row;
399 flex-wrap: nowrap;
400 padding: 1em;
401 }
402 body.chat #chat-config #chat-config-options .menu-entry label[for] {
403 cursor: pointer;
404 }
405 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
406 min-width: 1.5rem;
407 }
408 body.chat #chat-config #chat-config-options .menu-entry span.hint {
409 /* Config menu hint text */
410 font-size: 80%;
411 white-space: pre-wrap;
412 display: inline-block;
413 }
414 body.chat #chat-config #chat-config-options .menu-entry:first-child {
415 }
416 body.chat #chat-config #chat-config-options .menu-entry div.label-wrapper {
417 display: flex;
418 flex-direction: column;
419 align-self: baseline;
420 margin-left: 1em;
421 }
422 body.chat #chat-config #chat-config-options .menu-entry select {
 
423 }
424 body.chat #chat-preview #chat-preview-content {
425 overflow: auto;
426 flex: 1 1 auto;
427 padding: 0.5em;
@@ -405,10 +516,16 @@
516 }
517
518 body.chat #chat-clear-filter {
519 margin: 0.25em 0.5em;
520 }
521
522 body.chat.fossil-dark-style #chat-button-attach > svg {
523 /* The black paperclip is barely visible in dark-mode
524 skins when they have dark buttons */
525 filter: invert(0.8);
526 }
527
528 body.chat .anim-rotate-360 {
529 animation: rotate-360 750ms linear;
530 }
531 @keyframes rotate-360 {
532
+13 -9
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,19 +36,23 @@
3636
* The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
3737
pages by default. Use the new <tt>--all</tt> option to include deleted
3838
pages in the output.
3939
* The [/help?cmd=all|fossil all git status] command only shows reports for
4040
the subset of repositories that have a configured Git export.
41
- * Enhanced the [/help?cmd=/chat|/chat page] configuration and added the ability
42
- for a repository administrator to [./chat.md#notifications|extend the
43
- selection of notification sounds via unversioned files].
44
- * The [/help?cmd=/chat|/chat] messages now use fossil's full complement of
45
- markdown features, instead of the prior small subset of markup it
46
- previously supported. This retroactively applies to all chat messages,
47
- as they are markdown-processed when they are sent instead of when they
48
- are saved. Added a preview mode so messages can be previewed before
49
- being sent. See [./chat.md#usage|the chat docs] for more details.
41
+ * The [/help?cmd=/chat|/chat] configuration was reimplemented and
42
+ provides new options, including the ability for a repository
43
+ administrator to
44
+ [./chat.md#notifications|extend the selection of notification sounds]
45
+ using unversioned files.
46
+ * Chat now uses fossil's full complement of markdown features,
47
+ instead of the prior small subset of markup it previously supported.
48
+ This retroactively applies to all chat messages, as they are
49
+ markdown-processed when they are sent instead of when they
50
+ are saved.
51
+ * Added a chat message preview mode so messages can be previewed
52
+ before being sent. Similarly, added a per-message ability to view
53
+ the raw un-parsed message text.
5054
* The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit],
5155
[/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow]
5256
was changed from ctrl-enter to shift-enter in order to align with
5357
[/help?cmd=/chat|/chat]'s new preview feature and related future
5458
changes.
5559
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,19 +36,23 @@
36 * The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
37 pages by default. Use the new <tt>--all</tt> option to include deleted
38 pages in the output.
39 * The [/help?cmd=all|fossil all git status] command only shows reports for
40 the subset of repositories that have a configured Git export.
41 * Enhanced the [/help?cmd=/chat|/chat page] configuration and added the ability
42 for a repository administrator to [./chat.md#notifications|extend the
43 selection of notification sounds via unversioned files].
44 * The [/help?cmd=/chat|/chat] messages now use fossil's full complement of
45 markdown features, instead of the prior small subset of markup it
46 previously supported. This retroactively applies to all chat messages,
47 as they are markdown-processed when they are sent instead of when they
48 are saved. Added a preview mode so messages can be previewed before
49 being sent. See [./chat.md#usage|the chat docs] for more details.
 
 
 
 
50 * The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit],
51 [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow]
52 was changed from ctrl-enter to shift-enter in order to align with
53 [/help?cmd=/chat|/chat]'s new preview feature and related future
54 changes.
55
--- www/changes.wiki
+++ www/changes.wiki
@@ -36,19 +36,23 @@
36 * The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
37 pages by default. Use the new <tt>--all</tt> option to include deleted
38 pages in the output.
39 * The [/help?cmd=all|fossil all git status] command only shows reports for
40 the subset of repositories that have a configured Git export.
41 * The [/help?cmd=/chat|/chat] configuration was reimplemented and
42 provides new options, including the ability for a repository
43 administrator to
44 [./chat.md#notifications|extend the selection of notification sounds]
45 using unversioned files.
46 * Chat now uses fossil's full complement of markdown features,
47 instead of the prior small subset of markup it previously supported.
48 This retroactively applies to all chat messages, as they are
49 markdown-processed when they are sent instead of when they
50 are saved.
51 * Added a chat message preview mode so messages can be previewed
52 before being sent. Similarly, added a per-message ability to view
53 the raw un-parsed message text.
54 * The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit],
55 [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow]
56 was changed from ctrl-enter to shift-enter in order to align with
57 [/help?cmd=/chat|/chat]'s new preview feature and related future
58 changes.
59
+1 -1
--- www/hashes.md
+++ www/hashes.md
@@ -3,11 +3,11 @@
33
All artifacts in Fossil are identified by a unique hash, currently using
44
[the SHA3 algorithm by default][hpol], but historically using the SHA1
55
algorithm:
66
77
<table border="1" cellspacing="0" cellpadding="10">
8
-<tr><th>Algorithm<</th><th>Raw Bits</th> <th>Hexadecimal digits</th></tr>
8
+<tr><th>Algorithm</th><th>Raw Bits</th> <th>Hexadecimal digits</th></tr>
99
<tr><td>SHA3-256</td> <td>256</td> <td>64</td></tr>
1010
<tr><td>SHA1</td> <td>160</td> <td>40</td></tr>
1111
</table>
1212
1313
There are many types of artifacts in Fossil: commits (a.k.a. check-ins),
1414
--- www/hashes.md
+++ www/hashes.md
@@ -3,11 +3,11 @@
3 All artifacts in Fossil are identified by a unique hash, currently using
4 [the SHA3 algorithm by default][hpol], but historically using the SHA1
5 algorithm:
6
7 <table border="1" cellspacing="0" cellpadding="10">
8 <tr><th>Algorithm<</th><th>Raw Bits</th> <th>Hexadecimal digits</th></tr>
9 <tr><td>SHA3-256</td> <td>256</td> <td>64</td></tr>
10 <tr><td>SHA1</td> <td>160</td> <td>40</td></tr>
11 </table>
12
13 There are many types of artifacts in Fossil: commits (a.k.a. check-ins),
14
--- www/hashes.md
+++ www/hashes.md
@@ -3,11 +3,11 @@
3 All artifacts in Fossil are identified by a unique hash, currently using
4 [the SHA3 algorithm by default][hpol], but historically using the SHA1
5 algorithm:
6
7 <table border="1" cellspacing="0" cellpadding="10">
8 <tr><th>Algorithm</th><th>Raw Bits</th> <th>Hexadecimal digits</th></tr>
9 <tr><td>SHA3-256</td> <td>256</td> <td>64</td></tr>
10 <tr><td>SHA1</td> <td>160</td> <td>40</td></tr>
11 </table>
12
13 There are many types of artifacts in Fossil: commits (a.k.a. check-ins),
14

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button