Fossil SCM

Merged in latest trunk. test-markdown-render now accepts files and string literals.

stephan 2021-09-25 07:20 markdown-tagrefs merge
Commit 5618cceb7bf9e8c14108d9fd16c7b8f74cbb8994015e945bed8210e34723cf26
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -520,15 +520,18 @@
520520
button,
521521
input,
522522
optgroup,
523523
select,
524524
textarea {
525
- background-color: inherit;
525
+ background: inherit;
526526
color: inherit;
527527
font: inherit;
528528
margin: 0
529529
}
530
+button {
531
+ background-color: rgba(45,45,45,0.75);
532
+}
530533
input, textarea, select {
531534
border: 1px solid rgba(127, 201, 255, 0.9);
532535
padding: 1px;
533536
}
534537
select {
535538
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -520,15 +520,18 @@
520 button,
521 input,
522 optgroup,
523 select,
524 textarea {
525 background-color: inherit;
526 color: inherit;
527 font: inherit;
528 margin: 0
529 }
 
 
 
530 input, textarea, select {
531 border: 1px solid rgba(127, 201, 255, 0.9);
532 padding: 1px;
533 }
534 select {
535
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -520,15 +520,18 @@
520 button,
521 input,
522 optgroup,
523 select,
524 textarea {
525 background: inherit;
526 color: inherit;
527 font: inherit;
528 margin: 0
529 }
530 button {
531 background-color: rgba(45,45,45,0.75);
532 }
533 input, textarea, select {
534 border: 1px solid rgba(127, 201, 255, 0.9);
535 padding: 1px;
536 }
537 select {
538
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -237,11 +237,11 @@
237237
div.footer div {
238238
background-color: #222;
239239
box-shadow: 3px 3px 1px #000;
240240
border-radius: 0 0 1rem 1rem;
241241
margin: 0 0 10px 0;
242
- padding: 0.5rem 0.75rem;
242
+ padding: 0.25rem 0.75rem;
243243
}
244244
245245
div.footer div.page-time {
246246
float: left;
247247
}
@@ -1157,14 +1157,13 @@
11571157
body.chat div.header, body.chat div.footer,
11581158
body.chat div.mainmenu, body.chat div.submenu,
11591159
body.chat div.content {
11601160
margin-left: auto;
11611161
margin-right: auto;
1162
+ margin-top: auto/*eliminates unnecessary scrollbars*/;
11621163
}
11631164
body.chat.chat-only-mode div.content {
11641165
max-width: revert;
11651166
}
1166
-
1167
-body.chat .message-widget .message-widget-tab {
1168
- /* Make /chat user names and timestamps more visible */
1169
- filter: saturate(6);
1167
+body.chat #chat-user-list .chat-user{
1168
+ color: white;
11701169
}
11711170
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -237,11 +237,11 @@
237 div.footer div {
238 background-color: #222;
239 box-shadow: 3px 3px 1px #000;
240 border-radius: 0 0 1rem 1rem;
241 margin: 0 0 10px 0;
242 padding: 0.5rem 0.75rem;
243 }
244
245 div.footer div.page-time {
246 float: left;
247 }
@@ -1157,14 +1157,13 @@
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 }
1163 body.chat.chat-only-mode div.content {
1164 max-width: revert;
1165 }
1166
1167 body.chat .message-widget .message-widget-tab {
1168 /* Make /chat user names and timestamps more visible */
1169 filter: saturate(6);
1170 }
1171
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -237,11 +237,11 @@
237 div.footer div {
238 background-color: #222;
239 box-shadow: 3px 3px 1px #000;
240 border-radius: 0 0 1rem 1rem;
241 margin: 0 0 10px 0;
242 padding: 0.25rem 0.75rem;
243 }
244
245 div.footer div.page-time {
246 float: left;
247 }
@@ -1157,14 +1157,13 @@
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 body.chat #chat-user-list .chat-user{
1168 color: white;
 
 
1169 }
1170
+1 -1
--- src/builtin.c
+++ src/builtin.c
@@ -704,11 +704,11 @@
704704
** the final one! */
705705
} fjs[] = {
706706
/* This list ordering isn't strictly important. */
707707
{"confirmer", 0, 0},
708708
{"copybutton", 0, "dom\0"},
709
- {"diff", 0, "dom\0fetch\0popupwidget\0"},
709
+ {"diff", 0, "dom\0fetch\0"},
710710
{"dom", 0, 0},
711711
{"fetch", 0, 0},
712712
{"numbered-lines", 0, "popupwidget\0copybutton\0"},
713713
{"pikchr", 0, "dom\0"},
714714
{"popupwidget", 0, "dom\0"},
715715
--- src/builtin.c
+++ src/builtin.c
@@ -704,11 +704,11 @@
704 ** the final one! */
705 } fjs[] = {
706 /* This list ordering isn't strictly important. */
707 {"confirmer", 0, 0},
708 {"copybutton", 0, "dom\0"},
709 {"diff", 0, "dom\0fetch\0popupwidget\0"},
710 {"dom", 0, 0},
711 {"fetch", 0, 0},
712 {"numbered-lines", 0, "popupwidget\0copybutton\0"},
713 {"pikchr", 0, "dom\0"},
714 {"popupwidget", 0, "dom\0"},
715
--- src/builtin.c
+++ src/builtin.c
@@ -704,11 +704,11 @@
704 ** the final one! */
705 } fjs[] = {
706 /* This list ordering isn't strictly important. */
707 {"confirmer", 0, 0},
708 {"copybutton", 0, "dom\0"},
709 {"diff", 0, "dom\0fetch\0"},
710 {"dom", 0, 0},
711 {"fetch", 0, 0},
712 {"numbered-lines", 0, "popupwidget\0copybutton\0"},
713 {"pikchr", 0, "dom\0"},
714 {"popupwidget", 0, "dom\0"},
715
+16 -3
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,34 @@
181181
@ <input type="file" name="file" id="chat-input-file">
182182
@ </div>
183183
@ <div id="chat-drop-details"></div>
184184
@ </div>
185185
@ </div>
186
- @ <div id='chat-preview' class='hidden'>
186
+ @ <div id='chat-user-list-wrapper' class='hidden'>
187
+ @ <div class='legend'>
188
+ @ <span class='help-buttonlet'>
189
+ @ Users who have messages in the currently-loaded list.<br><br>
190
+ @ <strong>Tap a user name</strong> to filter messages
191
+ @ on that user and tap again to clear the filter.<br><br>
192
+ @ <strong>Tap the title</strong> of this widget to toggle
193
+ @ the list on and off.
194
+ @ </span>
195
+ @ <span>Active users (sorted by last message time)</span>
196
+ @ </div>
197
+ @ <div id='chat-user-list'></div>
198
+ @ </div>
199
+ @ <div id='chat-preview' class='hidden chat-view'>
187200
@ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
188201
@ <div id='chat-preview-content' class='message-widget-content'></div>
189202
@ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190203
@ </div>
191
- @ <div id='chat-config' class='hidden'>
204
+ @ <div id='chat-config' class='hidden chat-view'>
192205
@ <div id='chat-config-options'></div>
193206
/* ^^^populated client-side */
194207
@ <button>Close Settings</button>
195208
@ </div>
196
- @ <div id='chat-messages-wrapper'>
209
+ @ <div id='chat-messages-wrapper' class='chat-view'>
197210
/* New chat messages get inserted immediately after this element */
198211
@ <span id='message-inject-point'></span>
199212
@ </div>
200213
fossil_free(zProjectName);
201214
builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
202215
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,34 @@
181 @ <input type="file" name="file" id="chat-input-file">
182 @ </div>
183 @ <div id="chat-drop-details"></div>
184 @ </div>
185 @ </div>
186 @ <div id='chat-preview' class='hidden'>
 
 
 
 
 
 
 
 
 
 
 
 
 
187 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
188 @ <div id='chat-preview-content' class='message-widget-content'></div>
189 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
190 @ </div>
191 @ <div id='chat-config' class='hidden'>
192 @ <div id='chat-config-options'></div>
193 /* ^^^populated client-side */
194 @ <button>Close Settings</button>
195 @ </div>
196 @ <div id='chat-messages-wrapper'>
197 /* New chat messages get inserted immediately after this element */
198 @ <span id='message-inject-point'></span>
199 @ </div>
200 fossil_free(zProjectName);
201 builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
202
--- src/chat.c
+++ src/chat.c
@@ -181,21 +181,34 @@
181 @ <input type="file" name="file" id="chat-input-file">
182 @ </div>
183 @ <div id="chat-drop-details"></div>
184 @ </div>
185 @ </div>
186 @ <div id='chat-user-list-wrapper' class='hidden'>
187 @ <div class='legend'>
188 @ <span class='help-buttonlet'>
189 @ Users who have messages in the currently-loaded list.<br><br>
190 @ <strong>Tap a user name</strong> to filter messages
191 @ on that user and tap again to clear the filter.<br><br>
192 @ <strong>Tap the title</strong> of this widget to toggle
193 @ the list on and off.
194 @ </span>
195 @ <span>Active users (sorted by last message time)</span>
196 @ </div>
197 @ <div id='chat-user-list'></div>
198 @ </div>
199 @ <div id='chat-preview' class='hidden chat-view'>
200 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
201 @ <div id='chat-preview-content' class='message-widget-content'></div>
202 @ <div id='chat-preview-buttons'><button id='chat-preview-close'>Close Preview</button></div>
203 @ </div>
204 @ <div id='chat-config' class='hidden chat-view'>
205 @ <div id='chat-config-options'></div>
206 /* ^^^populated client-side */
207 @ <button>Close Settings</button>
208 @ </div>
209 @ <div id='chat-messages-wrapper' class='chat-view'>
210 /* New chat messages get inserted immediately after this element */
211 @ <span id='message-inject-point'></span>
212 @ </div>
213 fossil_free(zProjectName);
214 builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
215
+360 -106
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
3434
else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
3535
return false;
3636
};
3737
3838
const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
39
+
40
+ /**
41
+ Returns an almost-ISO8601 form of Date object d.
42
+ */
43
+ const iso8601ish = function(d){
44
+ return d.toISOString()
45
+ .replace('T',' ').replace(/\.\d+/,'')
46
+ .replace('Z', ' zulu');
47
+ };
48
+ /** Returns the local time string of Date object d, defaulting
49
+ to the current time. */
50
+ const localTimeString = function ff(d){
51
+ d || (d = new Date());
52
+ return [
53
+ d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
54
+ '-',pad2(d.getDate()),
55
+ ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
56
+ ':',pad2(d.getSeconds())
57
+ ].join('');
58
+ };
3959
4060
(function(){
4161
let dbg = document.querySelector('#debugMsg');
4262
if(dbg){
4363
/* This can inadvertently influence our flexbox layouts, so move
@@ -74,11 +94,11 @@
7494
ht = wh - extra;
7595
}
7696
f.contentArea.style.height =
7797
f.contentArea.style.maxHeight = [
7898
"calc(", (ht>=100 ? ht : 100), "px",
79
- " - 1em"/*fudge value*/,")"
99
+ " - 0.75em"/*fudge value*/,")"
80100
/* ^^^^ hypothetically not needed, but both Chrome/FF on
81101
Linux will force scrollbars on the body if this value is
82102
too small (<0.75em in my tests). */
83103
].join('');
84104
if(false){
@@ -105,21 +125,24 @@
105125
pageTitle: E1('head title'),
106126
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
107127
inputWrapper: E1("#chat-input-area"),
108128
inputLine: E1('#chat-input-line'),
109129
fileSelectWrapper: E1('#chat-input-file-area'),
110
- messagesWrapper: E1('#chat-messages-wrapper'),
130
+ viewMessages: E1('#chat-messages-wrapper'),
111131
btnSubmit: E1('#chat-message-submit'),
112132
inputSingle: E1('#chat-input-single'),
113133
inputMulti: E1('#chat-input-multi'),
114134
inputCurrent: undefined/*one of inputSingle or inputMulti*/,
115135
inputFile: E1('#chat-input-file'),
116136
contentDiv: E1('div.content'),
117
- configArea: E1('#chat-config'),
118
- previewArea: E1('#chat-preview'),
137
+ viewConfig: E1('#chat-config'),
138
+ viewPreview: E1('#chat-preview'),
119139
previewContent: E1('#chat-preview-content'),
120
- btnPreview: E1('#chat-preview-button')
140
+ btnPreview: E1('#chat-preview-button'),
141
+ views: document.querySelectorAll('.chat-view'),
142
+ activeUserListWrapper: E1('#chat-user-list-wrapper'),
143
+ activeUserList: E1('#chat-user-list')
121144
},
122145
me: F.user.name,
123146
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
124147
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
125148
pageIsActive: 'visible'===document.visibilityState,
@@ -127,10 +150,23 @@
127150
notificationBubbleColor: 'white',
128151
totalMessageCount: 0, // total # of inbound messages
129152
//! Number of messages to load for the history buttons
130153
loadMessageCount: Math.abs(F.config.chat.initSize || 20),
131154
ajaxInflight: 0,
155
+ usersLastSeen:{
156
+ /* Map of user names to their most recent message time
157
+ (JS Date object). Only messages received by the chat client
158
+ are considered. */
159
+ /* Reminder: to convert a Julian time J to JS:
160
+ new Date((J - 2440587.5) * 86400000) */
161
+ },
162
+ filterState:{
163
+ activeUser: undefined,
164
+ match: function(uname){
165
+ return this.activeUser===uname || !this.activeUser;
166
+ }
167
+ },
132168
/** Gets (no args) or sets (1 arg) the current input text field value,
133169
taking into account single- vs multi-line input. The getter returns
134170
a string and the setter returns this object. */
135171
inputValue: function(){
136172
const e = this.inputElement();
@@ -157,19 +193,20 @@
157193
this.e.inputLine.classList.remove('single-line');
158194
}else{
159195
this.e.inputCurrent = this.e.inputSingle;
160196
this.e.inputLine.classList.add('single-line');
161197
}
162
- const m = this.e.messagesWrapper,
198
+ const m = this.e.viewMessages,
163199
sTop = m.scrollTop,
164200
mh1 = m.clientHeight;
165201
D.addClass(old, 'hidden');
166202
D.removeClass(this.e.inputCurrent, 'hidden');
167203
const mh2 = m.clientHeight;
168204
m.scrollTo(0, sTop + (mh1-mh2));
169205
this.e.inputCurrent.value = old.value;
170206
old.value = '';
207
+ this.animate(this.e.inputCurrent, "anim-flip-v");
171208
return this;
172209
},
173210
/**
174211
If passed true or no arguments, switches to multi-line mode
175212
if currently in single-line mode. If passed false, switches
@@ -241,12 +278,15 @@
241278
/* Injects DOM element e as a new row in the chat, at the oldest
242279
end of the list if atEnd is truthy, else at the newest end of
243280
the list. */
244281
injectMessageElem: function f(e, atEnd){
245282
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
246
- holder = this.e.messagesWrapper,
283
+ holder = this.e.viewMessages,
247284
prevMessage = this.e.newestMessage;
285
+ if(!this.filterState.match(e.dataset.xfrom)){
286
+ e.classList.add('hidden');
287
+ }
248288
if(atEnd){
249289
const fe = mip.nextElementSibling;
250290
if(fe) mip.parentNode.insertBefore(e, fe);
251291
else D.append(mip.parentNode, e);
252292
}else{
@@ -337,22 +377,22 @@
337377
<0 = top of the message list, >0 = bottom of the message list,
338378
0 == the newest message (normally the same position as >1).
339379
*/
340380
scrollMessagesTo: function(where){
341381
if(where<0){
342
- Chat.e.messagesWrapper.scrollTop = 0;
382
+ Chat.e.viewMessages.scrollTop = 0;
343383
}else if(where>0){
344
- Chat.e.messagesWrapper.scrollTop = Chat.e.messagesWrapper.scrollHeight;
384
+ Chat.e.viewMessages.scrollTop = Chat.e.viewMessages.scrollHeight;
345385
}else if(Chat.e.newestMessage){
346386
Chat.e.newestMessage.scrollIntoView(false);
347387
}
348388
},
349389
toggleChatOnlyMode: function(){
350390
return this.chatOnlyMode(!this.isChatOnlyMode());
351391
},
352392
messageIsInView: function(e){
353
- return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
393
+ return e ? overlapsElemView(e, this.e.viewMessages) : false;
354394
},
355395
settings:{
356396
get: (k,dflt)=>F.storage.get(k,dflt),
357397
getBool: (k,dflt)=>F.storage.getBool(k,dflt),
358398
set: (k,v)=>F.storage.set(k,v),
@@ -366,11 +406,13 @@
366406
defaults:{
367407
"images-inline": !!F.config.chat.imagesInline,
368408
"edit-multiline": false,
369409
"monospace-messages": false,
370410
"chat-only-mode": false,
371
- "audible-alert": true
411
+ "audible-alert": true,
412
+ "active-user-list": false,
413
+ "active-user-list-timestamps": false
372414
}
373415
},
374416
/** Plays a new-message notification sound IF the audible-alert
375417
setting is true, else this is a no-op. Returns this.
376418
*/
@@ -395,12 +437,117 @@
395437
setNewMessageSound: function f(uri){
396438
delete this.playNewMessageSound.audio;
397439
this.playNewMessageSound.uri = uri;
398440
this.settings.set('audible-alert', uri);
399441
return this;
442
+ },
443
+ /**
444
+ Expects e to be one of the elements in this.e.views.
445
+ The 'hidden' class is removed from e and added to
446
+ all other elements in that list. Returns e.
447
+ */
448
+ setCurrentView: function(e){
449
+ if(e===this.e.currentView){
450
+ return e;
451
+ }
452
+ this.e.views.forEach(function(E){
453
+ if(e!==E) D.addClass(E,'hidden');
454
+ });
455
+ this.e.currentView = D.removeClass(e,'hidden');
456
+ this.animate(this.e.currentView, 'anim-fade-in-fast');
457
+ return this.e.currentView;
458
+ },
459
+ /**
460
+ Updates the "active user list" view if we are not currently
461
+ batch-loading messages and if the active user list UI element
462
+ is active.
463
+ */
464
+ updateActiveUserList: function callee(){
465
+ if(this._isBatchLoading
466
+ || this.e.activeUserListWrapper.classList.contains('hidden')){
467
+ return this;
468
+ }else if(!callee.sortUsersSeen){
469
+ /** Array.sort() callback. Expects an array of user names and
470
+ sorts them in last-received message order (newest first). */
471
+ const self = this;
472
+ callee.sortUsersSeen = function(l,r){
473
+ l = self.usersLastSeen[l];
474
+ r = self.usersLastSeen[r];
475
+ if(l && r) return r - l;
476
+ else if(l) return -1;
477
+ else if(r) return 1;
478
+ else return 0;
479
+ };
480
+ callee.addUserElem = function(u){
481
+ const uSpan = D.addClass(D.span(), 'chat-user');
482
+ const uDate = self.usersLastSeen[u];
483
+ if(self.filterState.activeUser===u){
484
+ uSpan.classList.add('selected');
485
+ }
486
+ uSpan.dataset.uname = u;
487
+ D.append(uSpan, u, "\n",
488
+ D.append(
489
+ D.addClass(D.span(),'timestamp'),
490
+ localTimeString(uDate)//.substr(5/*chop off year*/)
491
+ ));
492
+ if(uDate.$uColor){
493
+ uSpan.style.backgroundColor = uDate.$uColor;
494
+ }
495
+ D.append(self.e.activeUserList, uSpan);
496
+ };
497
+ }
498
+ //D.clearElement(this.e.activeUserList);
499
+ D.remove(this.e.activeUserList.querySelectorAll('.chat-user'));
500
+ Object.keys(this.usersLastSeen).sort(
501
+ callee.sortUsersSeen
502
+ ).forEach(callee.addUserElem);
503
+ return this;
504
+ },
505
+ /**
506
+ Applies user name filter to all current messages, or clears
507
+ the filter if uname is falsy.
508
+ */
509
+ setUserFilter: function(uname){
510
+ this.filterState.activeUser = uname;
511
+ const mw = this.e.viewMessages.querySelectorAll('.message-widget');
512
+ const self = this;
513
+ let eLast;
514
+ if(!uname){
515
+ D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'),
516
+ 'hidden');
517
+ }else{
518
+ mw.forEach(function(w){
519
+ if(self.filterState.match(w.dataset.xfrom)){
520
+ w.classList.remove('hidden');
521
+ eLast = w;
522
+ }else{
523
+ w.classList.add('hidden');
524
+ }
525
+ });
526
+ }
527
+ if(eLast) eLast.scrollIntoView(false);
528
+ else this.scrollMessagesTo(1);
529
+ cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
530
+ e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
531
+ });
532
+ return this;
533
+ },
534
+
535
+ /**
536
+ If animations are enabled, passes its arguments
537
+ to D.addClassBriefly(), else this is a no-op.
538
+ If cb is a function, it is called after the
539
+ CSS class is removed. Returns this object;
540
+ */
541
+ animate: function f(e,a,cb){
542
+ if(!f.$disabled){
543
+ D.addClassBriefly(e, a, 0, cb);
544
+ }
545
+ return this;
400546
}
401547
};
548
+ cs.animate.$disabled = true;
402549
F.fetch.beforesend = ()=>cs.ajaxStart();
403550
F.fetch.aftersend = ()=>cs.ajaxEnd();
404551
cs.e.inputCurrent = cs.e.inputSingle;
405552
/* Install default settings... */
406553
Object.keys(cs.settings.defaults).forEach(function(k){
@@ -415,10 +562,16 @@
415562
tall vs wide. Can be toggled via settings popup. */
416563
document.body.classList.add('my-messages-right');
417564
}
418565
if(cs.settings.getBool('monospace-messages',false)){
419566
document.body.classList.add('monospace-messages');
567
+ }
568
+ if(cs.settings.getBool('active-user-list',false)){
569
+ cs.e.activeUserListWrapper.classList.remove('hidden');
570
+ }
571
+ if(cs.settings.getBool('active-user-list-timestamps',false)){
572
+ cs.e.activeUserList.classList.add('timestamps');
420573
}
421574
cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
422575
cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
423576
cs.pageTitleOrig = cs.e.pageTitle.innerText;
424577
const qs = (e)=>document.querySelector(e);
@@ -611,10 +764,37 @@
611764
cs.pageIsActive = ('visible' === document.visibilityState);
612765
if(cs.pageIsActive){
613766
cs.e.pageTitle.innerText = cs.pageTitleOrig;
614767
}
615768
}, true);
769
+ cs.setCurrentView(cs.e.viewMessages);
770
+
771
+ cs.e.activeUserList.addEventListener('click', function f(ev){
772
+ /* Filter messages on a user clicked in activeUserList */
773
+ ev.stopPropagation();
774
+ ev.preventDefault();
775
+ let eUser = ev.target;
776
+ while(eUser!==this && !eUser.classList.contains('chat-user')){
777
+ eUser = eUser.parentNode;
778
+ }
779
+ if(eUser==this || !eUser) return false;
780
+ const uname = eUser.dataset.uname;
781
+ let eLast;
782
+ cs.setCurrentView(cs.e.viewMessages);
783
+ if(eUser.classList.contains('selected')){
784
+ /* If curently selected, toggle filter off */
785
+ eUser.classList.remove('selected');
786
+ cs.setUserFilter(false);
787
+ delete f.$eSelected;
788
+ }else{
789
+ if(f.$eSelected) f.$eSelected.classList.remove('selected');
790
+ f.$eSelected = eUser;
791
+ eUser.classList.add('selected');
792
+ cs.setUserFilter(uname);
793
+ }
794
+ return false;
795
+ }, false);
616796
return cs;
617797
})()/*Chat initialization*/;
618798
619799
/**
620800
Custom widget type for rendering messages (one message per
@@ -658,21 +838,10 @@
658838
d.getHours(),":",
659839
(d.getMinutes()+100).toString().slice(1,3),
660840
' ', dowMap[d.getDay()]
661841
].join('');
662842
};
663
- /** Returns the local time string of Date object d, defaulting
664
- to the current time. */
665
- const localTimeString = function ff(d){
666
- d || (d = new Date());
667
- return [
668
- d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
669
- '-',pad2(d.getDate()),
670
- ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
671
- ':',pad2(d.getSeconds())
672
- ].join('');
673
- };
674843
cf.prototype = {
675844
scrollIntoView: function(){
676845
this.e.content.scrollIntoView();
677846
},
678847
setMessage: function(m){
@@ -758,14 +927,14 @@
758927
eXFrom.addEventListener('click', ()=>this.e.tab.click(), false);
759928
}*/
760929
return this;
761930
},
762931
/* Event handler for clicking .message-user elements to show their
763
- timestamps. */
932
+ timestamps and a set of actions. */
764933
_handleLegendClicked: function f(ev){
765934
if(!f.popup){
766
- /* Timestamp popup widget */
935
+ /* "Popup" widget */
767936
f.popup = {
768937
e: D.addClass(D.div(), 'chat-message-popup'),
769938
refresh:function(){
770939
const eMsg = this.$eMsg/*.message-widget element*/;
771940
if(!eMsg) return;
@@ -830,18 +999,45 @@
830999
y: 'a'
8311000
}), "User's Timeline"),
8321001
'target', '_blank'
8331002
);
8341003
D.append(toolbar2, timelineLink);
1004
+ if(Chat.filterState.activeUser &&
1005
+ Chat.filterState.match(eMsg.dataset.xfrom)){
1006
+ /* Add a button to clear user filter and jump to
1007
+ this message in its original context. */
1008
+ D.append(
1009
+ this.e,
1010
+ D.append(
1011
+ D.addClass(D.div(), 'toolbar'),
1012
+ D.button(
1013
+ "Message in context",
1014
+ function(){
1015
+ self.hide();
1016
+ Chat.setUserFilter(false);
1017
+ eMsg.scrollIntoView(false);
1018
+ Chat.animate(
1019
+ eMsg.firstElementChild, 'anim-flip-h'
1020
+ //eMsg.firstElementChild, 'anim-flip-v'
1021
+ //eMsg.childNodes, 'anim-rotate-360'
1022
+ //eMsg.childNodes, 'anim-flip-v'
1023
+ //eMsg, 'anim-flip-v'
1024
+ );
1025
+ })
1026
+ )
1027
+ );
1028
+ }/*jump-to button*/
8351029
}
8361030
const tab = eMsg.querySelector('.message-widget-tab');
8371031
D.append(tab, this.e);
8381032
D.removeClass(this.e, 'hidden');
1033
+ Chat.animate(this.e, 'anim-fade-in-fast');
8391034
}/*refresh()*/,
8401035
hide: function(){
841
- D.addClass(D.clearElement(this.e), 'hidden');
8421036
delete this.$eMsg;
1037
+ D.addClass(this.e, 'hidden');
1038
+ D.clearElement(this.e);
8431039
},
8441040
show: function(tgtMsg){
8451041
if(tgtMsg === this.$eMsg){
8461042
this.hide();
8471043
return;
@@ -961,11 +1157,11 @@
9611157
*/
9621158
Chat.submitMessage = function f(){
9631159
if(!f.spaces){
9641160
f.spaces = /\s+$/;
9651161
}
966
- this.revealPreview(false);
1162
+ this.setCurrentView(this.e.viewMessages);
9671163
const fd = new FormData();
9681164
var msg = this.inputValue().trim();
9691165
if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
9701166
/* Cosmetic: trim whitespace from the ends of lines to try to
9711167
keep copy/paste from terminals, especially wide ones, from
@@ -1021,42 +1217,102 @@
10211217
e.preventDefault();
10221218
Chat.submitMessage();
10231219
return false;
10241220
});
10251221
1026
- /* Returns an almost-ISO8601 form of Date object d. */
1027
- const iso8601ish = function(d){
1028
- return d.toISOString()
1029
- .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
1030
- };
1031
-
10321222
(function(){/*Set up #chat-settings-button */
10331223
const settingsButton = document.querySelector('#chat-settings-button');
10341224
const optionsMenu = E1('#chat-config-options');
10351225
const cbToggle = function(ev){
10361226
ev.preventDefault();
10371227
ev.stopPropagation();
1038
- if(Chat.e.configArea.classList.contains('hidden')){
1039
- D.removeClass(Chat.e.configArea, 'hidden');
1040
- D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1041
- }else{
1042
- D.addClass(Chat.e.configArea, 'hidden');
1043
- D.removeClass(Chat.e.messagesWrapper, 'hidden');
1044
- }
1228
+ Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
1229
+ ? Chat.e.viewMessages : Chat.e.viewConfig);
10451230
return false;
10461231
};
10471232
D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1048
- Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1049
- /* Settings menu entries... */
1233
+ Chat.e.viewConfig.querySelector('button').addEventListener('click', cbToggle, false);
1234
+
1235
+ /** Internal acrobatics to allow certain settings toggles to access
1236
+ related toggles. */
1237
+ const namedOptions = {
1238
+ activeUsers:{
1239
+ label: "Show active users list",
1240
+ boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1241
+ persistentSetting: 'active-user-list',
1242
+ callback: function(){
1243
+ D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1244
+ D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1245
+ if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1246
+ /* When hiding this element, undo all filtering */
1247
+ Chat.setUserFilter(false);
1248
+ /*Ideally we'd scroll the final message into view
1249
+ now, but because viewMessages is currently hidden behind
1250
+ viewConfig, scrolling is a no-op. */
1251
+ Chat.scrollMessagesTo(1);
1252
+ }else{
1253
+ Chat.updateActiveUserList();
1254
+ Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1255
+ }
1256
+ }
1257
+ }
1258
+ };
1259
+ if(1){
1260
+ /* Per user request, toggle the list of users on and off if the
1261
+ legend element is tapped. */
1262
+ const optAu = namedOptions.activeUsers;
1263
+ optAu.theLegend = Chat.e.activeUserListWrapper.firstElementChild/*LEGEND*/;
1264
+ optAu.theList = optAu.theLegend.nextElementSibling/*user list container*/;
1265
+ optAu.theLegend.addEventListener('click',function(){
1266
+ D.toggleClass(Chat.e.activeUserListWrapper, 'collapsed');
1267
+ if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1268
+ Chat.animate(optAu.theList,'anim-flip-v');
1269
+ }
1270
+ }, false);
1271
+ }/*namedOptions.activeUsers additional setup*/
1272
+ /* Settings menu entries... Remember that they will be rendered in
1273
+ reverse order and the most frequently-needed ones "should"
1274
+ (arguably) be closer to the start of this list so that they
1275
+ will be rendered within easier reach of the settings button. */
10501276
const settingsOps = [{
10511277
label: "Multi-line input",
10521278
boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
10531279
persistentSetting: 'edit-multiline',
10541280
callback: function(){
10551281
Chat.inputToggleSingleMulti();
10561282
}
10571283
},{
1284
+ label: "Left-align my posts",
1285
+ boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1286
+ callback: function f(){
1287
+ document.body.classList.toggle('my-messages-right');
1288
+ }
1289
+ },{
1290
+ label: "Show images inline",
1291
+ boolValue: ()=>Chat.settings.getBool('images-inline'),
1292
+ callback: function(){
1293
+ const v = Chat.settings.toggle('images-inline');
1294
+ F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1295
+ }
1296
+ },{
1297
+ label: "Timestamps in active users list",
1298
+ boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1299
+ persistentSetting: 'active-user-list-timestamps',
1300
+ callback: function(){
1301
+ D.toggleClass(Chat.e.activeUserList,'timestamps');
1302
+ /* If the timestamp option is activated but
1303
+ namedOptions.activeUsers is not currently checked then
1304
+ toggle that option on as well. */
1305
+ if(Chat.e.activeUserList.classList.contains('timestamps')
1306
+ && !namedOptions.activeUsers.boolValue()){
1307
+ namedOptions.activeUsers.checkbox.checked = true;
1308
+ namedOptions.activeUsers.callback();
1309
+ Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1310
+ }
1311
+ }
1312
+ },
1313
+ namedOptions.activeUsers,{
10581314
label: "Monospace message font",
10591315
boolValue: ()=>document.body.classList.contains('monospace-messages'),
10601316
persistentSetting: 'monospace-messages',
10611317
callback: function(){
10621318
document.body.classList.toggle('monospace-messages');
@@ -1066,57 +1322,44 @@
10661322
boolValue: ()=>Chat.isChatOnlyMode(),
10671323
persistentSetting: 'chat-only-mode',
10681324
callback: function(){
10691325
Chat.toggleChatOnlyMode();
10701326
}
1071
- },{
1072
- label: "Left-align my posts",
1073
- boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1074
- callback: function f(){
1075
- document.body.classList.toggle('my-messages-right');
1076
- }
1077
- },{
1078
- label: "Images inline",
1079
- boolValue: ()=>Chat.settings.getBool('images-inline'),
1080
- callback: function(){
1081
- const v = Chat.settings.toggle('images-inline');
1082
- F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1083
- }
10841327
}];
10851328
10861329
/** Set up selection list of notification sounds. */
10871330
if(1){
1088
- settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
10891331
const selectSound = D.select();
1090
- D.append(settingsOps.selectSound,
1091
- D.append(D.span(),"Audio alert"),
1092
- selectSound);
10931332
D.option(selectSound, "", "(no audio)");
10941333
const firstSoundIndex = selectSound.options.length;
1095
- F.config.chat.alerts.forEach(function(a){
1096
- D.option(selectSound, a);
1097
- });
1334
+ F.config.chat.alerts.forEach((a)=>D.option(selectSound, a));
10981335
if(true===Chat.settings.getBool('audible-alert')){
1336
+ /* This setting used to be a plain bool. If we encounter
1337
+ such a setting, take the first sound in the list. */
10991338
selectSound.selectedIndex = firstSoundIndex;
11001339
}else{
11011340
selectSound.value = Chat.settings.get('audible-alert','');
11021341
if(selectSound.selectedIndex<0){
1103
- /*Missing file - removed after this setting was applied. Fall back
1104
- to the first sound in the list. */
1342
+ /* Missing file - removed after this setting was
1343
+ applied. Fall back to the first sound in the list. */
11051344
selectSound.selectedIndex = firstSoundIndex;
11061345
}
11071346
}
1108
- selectSound.addEventListener('change',function(){
1109
- const v = this.value;
1110
- Chat.setNewMessageSound(v);
1111
- F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1112
- if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1113
- }, false);
11141347
Chat.setNewMessageSound(selectSound.value);
1348
+ settingsOps.push({
1349
+ label: "Audio alert",
1350
+ select: selectSound,
1351
+ callback: function(ev){
1352
+ const v = ev.target.value;
1353
+ Chat.setNewMessageSound(v);
1354
+ F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1355
+ if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1356
+ }
1357
+ });
11151358
}/*audio notification config*/
11161359
/**
1117
- Build list of options...
1360
+ Build UI for config options...
11181361
*/
11191362
settingsOps.forEach(function f(op){
11201363
const line = D.addClass(D.div(), 'menu-entry');
11211364
const btn = D.append(
11221365
D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
@@ -1125,62 +1368,52 @@
11251368
op.callback(ev);
11261369
if(op.persistentSetting){
11271370
Chat.settings.set(op.persistentSetting, op.boolValue());
11281371
}
11291372
};
1130
- if(op.hasOwnProperty('boolValue')){
1373
+ if(op.hasOwnProperty('select')){
1374
+ D.append(line, btn, op.select);
1375
+ op.select.addEventListener('change', callback, false);
1376
+ }else if(op.hasOwnProperty('boolValue')){
11311377
if(undefined === f.$id) f.$id = 0;
11321378
++f.$id;
1133
- const check = D.attr(D.checkbox(1, op.boolValue()),
1134
- 'aria-label', op.label);
1379
+ const check = op.checkbox
1380
+ = D.attr(D.checkbox(1, op.boolValue()),
1381
+ 'aria-label', op.label);
11351382
const id = 'cfgopt'+f.$id;
11361383
if(op.boolValue()) check.checked = true;
11371384
D.attr(check, 'id', id);
11381385
D.attr(btn, 'for', id);
11391386
D.append(line, check);
11401387
check.addEventListener('change', callback);
1388
+ D.append(line, btn);
11411389
}else{
11421390
line.addEventListener('click', callback);
1391
+ D.append(line, btn);
11431392
}
1144
- D.append(line, btn);
11451393
D.append(optionsMenu, line);
11461394
});
1147
- if(settingsOps.selectSound){
1395
+ if(0 && settingsOps.selectSound){
11481396
D.append(optionsMenu, settingsOps.selectSound);
11491397
}
11501398
//settingsButton.click()/*for for development*/;
11511399
})()/*#chat-settings-button setup*/;
11521400
11531401
(function(){/*set up message preview*/
11541402
const btnPreview = Chat.e.btnPreview;
11551403
Chat.setPreviewText = function(t){
1156
- this.revealPreview(true).e.previewContent.innerHTML = t;
1157
- this.e.previewArea.querySelectorAll('a').forEach(addAnchorTargetBlank);
1404
+ this.setCurrentView(this.e.viewPreview);
1405
+ this.e.previewContent.innerHTML = t;
1406
+ this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
11581407
this.e.inputCurrent.focus();
11591408
};
1160
- /**
1161
- Reveals preview area if showIt is true, else hides it.
1162
- This also shows/hides other elements, "as appropriate."
1163
- */
1164
- Chat.revealPreview = function(showIt){
1165
- if(showIt){
1166
- D.removeClass(Chat.e.previewArea, 'hidden');
1167
- D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1168
- 'hidden');
1169
- }else{
1170
- D.addClass([Chat.e.configArea, Chat.e.previewArea], 'hidden');
1171
- D.removeClass(Chat.e.messagesWrapper, 'hidden');
1172
- }
1173
- return this;
1174
- };
1175
- Chat.e.previewArea.querySelector('#chat-preview-close').
1176
- addEventListener('click', ()=>Chat.revealPreview(false), false);
1409
+ Chat.e.viewPreview.querySelector('#chat-preview-close').
1410
+ addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
11771411
let previewPending = false;
11781412
const elemsToEnable = [
11791413
btnPreview, Chat.e.btnSubmit,
11801414
Chat.e.inputSingle, Chat.e.inputMulti];
1181
- Chat.disableDuringAjax.push(btnPreview);
11821415
const submit = function(ev){
11831416
ev.preventDefault();
11841417
ev.stopPropagation();
11851418
if(previewPending) return false;
11861419
const txt = Chat.e.inputCurrent.value;
@@ -1209,12 +1442,12 @@
12091442
previewPending = true;
12101443
Chat.setPreviewText("Loading preview...");
12111444
},
12121445
aftersend:function(){
12131446
previewPending = false;
1214
- D.enable(elemsToEnable);
12151447
Chat.ajaxEnd();
1448
+ D.enable(elemsToEnable);
12161449
}
12171450
});
12181451
return false;
12191452
};
12201453
btnPreview.addEventListener('click', submit, false);
@@ -1231,10 +1464,18 @@
12311464
should only be true when loading older messages. */
12321465
f.processPost = function(m,atEnd){
12331466
++Chat.totalMessageCount;
12341467
if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
12351468
if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
1469
+ if(m.xfrom && m.mtime){
1470
+ const d = new Date(m.mtime);
1471
+ const uls = Chat.usersLastSeen[m.xfrom];
1472
+ if(!uls || uls<d){
1473
+ d.$uColor = m.uclr;
1474
+ Chat.usersLastSeen[m.xfrom] = d;
1475
+ }
1476
+ }
12361477
if( m.mdel ){
12371478
/* A record deletion notice. */
12381479
Chat.deleteMessageElem(m.mdel);
12391480
return;
12401481
}
@@ -1247,10 +1488,11 @@
12471488
Chat._gotServerError = m;
12481489
}
12491490
}/*processPost()*/;
12501491
}/*end static init*/
12511492
jx.msgs.forEach((m)=>f.processPost(m,atEnd));
1493
+ Chat.updateActiveUserList();
12521494
if('visible'===document.visibilityState){
12531495
if(Chat.changesSincePageHidden){
12541496
Chat.changesSincePageHidden = 0;
12551497
Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
12561498
}
@@ -1273,14 +1515,14 @@
12731515
D.fieldset(loadLegend), "id", "load-msg-toolbar"
12741516
);
12751517
Chat.disableDuringAjax.push(toolbar);
12761518
/* Loads the next n oldest messages, or all previous history if n is negative. */
12771519
const loadOldMessages = function(n){
1278
- Chat.e.messagesWrapper.classList.add('loading');
1520
+ Chat.e.viewMessages.classList.add('loading');
12791521
Chat._isBatchLoading = true;
1280
- const scrollHt = Chat.e.messagesWrapper.scrollHeight,
1281
- scrollTop = Chat.e.messagesWrapper.scrollTop;
1522
+ const scrollHt = Chat.e.viewMessages.scrollHeight,
1523
+ scrollTop = Chat.e.viewMessages.scrollTop;
12821524
F.fetch("chat-poll",{
12831525
urlParams:{
12841526
before: Chat.mnMsg,
12851527
n: n
12861528
},
@@ -1291,10 +1533,11 @@
12911533
},
12921534
onload:function(x){
12931535
let gotMessages = x.msgs.length;
12941536
newcontent(x,true);
12951537
Chat._isBatchLoading = false;
1538
+ Chat.updateActiveUserList();
12961539
if(Chat._gotServerError){
12971540
Chat._gotServerError = false;
12981541
return;
12991542
}
13001543
if(n<0/*we asked for all history*/
@@ -1314,17 +1557,17 @@
13141557
}
13151558
if(gotMessages > 0){
13161559
F.toast.message("Loaded "+gotMessages+" older messages.");
13171560
/* Return scroll position to where it was when the history load
13181561
was requested, per user request */
1319
- Chat.e.messagesWrapper.scrollTo(
1320
- 0, Chat.e.messagesWrapper.scrollHeight - scrollHt + scrollTop
1562
+ Chat.e.viewMessages.scrollTo(
1563
+ 0, Chat.e.viewMessages.scrollHeight - scrollHt + scrollTop
13211564
);
13221565
}
13231566
},
13241567
aftersend:function(){
1325
- Chat.e.messagesWrapper.classList.remove('loading');
1568
+ Chat.e.viewMessages.classList.remove('loading');
13261569
Chat.ajaxEnd();
13271570
}
13281571
});
13291572
};
13301573
const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1333,19 +1576,19 @@
13331576
D.append(wrapper, btn);
13341577
btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
13351578
btn = D.button("All previous messages");
13361579
D.append(wrapper, btn);
13371580
btn.addEventListener('click',()=>loadOldMessages(-1));
1338
- D.append(Chat.e.messagesWrapper, toolbar);
1581
+ D.append(Chat.e.viewMessages, toolbar);
13391582
toolbar.disabled = true /*will be enabled when msg load finishes */;
13401583
})()/*end history loading widget setup*/;
13411584
13421585
const afterFetch = function f(){
13431586
if(true===f.isFirstCall){
13441587
f.isFirstCall = false;
13451588
Chat.ajaxEnd();
1346
- Chat.e.messagesWrapper.classList.remove('loading');
1589
+ Chat.e.viewMessages.classList.remove('loading');
13471590
setTimeout(function(){
13481591
Chat.scrollMessagesTo(1);
13491592
}, 250);
13501593
}
13511594
if(Chat._gotServerError && Chat.intervalTimer){
@@ -1363,11 +1606,11 @@
13631606
f.running = true;
13641607
Chat._isBatchLoading = f.isFirstCall;
13651608
if(true===f.isFirstCall){
13661609
f.isFirstCall = false;
13671610
Chat.ajaxStart();
1368
- Chat.e.messagesWrapper.classList.add('loading');
1611
+ Chat.e.viewMessages.classList.add('loading');
13691612
}
13701613
F.fetch("chat-poll",{
13711614
timeout: 420 * 1000/*FIXME: get the value from the server*/,
13721615
urlParams:{
13731616
name: Chat.mxMsg
@@ -1384,11 +1627,14 @@
13841627
resumed, and reportError() produces a loud error message. */
13851628
afterFetch();
13861629
},
13871630
onload:function(y){
13881631
newcontent(y);
1389
- Chat._isBatchLoading = false;
1632
+ if(Chat._isBatchLoading){
1633
+ Chat._isBatchLoading = false;
1634
+ Chat.updateActiveUserList();
1635
+ }
13901636
afterFetch();
13911637
}
13921638
});
13931639
};
13941640
poll.isFirstCall = true;
@@ -1395,7 +1641,15 @@
13951641
Chat._gotServerError = poll.running = false;
13961642
if( window.fossil.config.chat.fromcli ){
13971643
Chat.chatOnlyMode(true);
13981644
}
13991645
Chat.intervalTimer = setInterval(poll, 1000);
1646
+ if(0){
1647
+ const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1648
+ document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1649
+ e.addEventListener('click',flip, false);
1650
+ });
1651
+ }
1652
+ setTimeout( ()=>Chat.inputFocus(), 0 );
1653
+ Chat.animate.$disabled = false;
14001654
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
14011655
})();
14021656
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
34 else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
35 return false;
36 };
37
38 const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
40 (function(){
41 let dbg = document.querySelector('#debugMsg');
42 if(dbg){
43 /* This can inadvertently influence our flexbox layouts, so move
@@ -74,11 +94,11 @@
74 ht = wh - extra;
75 }
76 f.contentArea.style.height =
77 f.contentArea.style.maxHeight = [
78 "calc(", (ht>=100 ? ht : 100), "px",
79 " - 1em"/*fudge value*/,")"
80 /* ^^^^ hypothetically not needed, but both Chrome/FF on
81 Linux will force scrollbars on the body if this value is
82 too small (<0.75em in my tests). */
83 ].join('');
84 if(false){
@@ -105,21 +125,24 @@
105 pageTitle: E1('head title'),
106 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
107 inputWrapper: E1("#chat-input-area"),
108 inputLine: E1('#chat-input-line'),
109 fileSelectWrapper: E1('#chat-input-file-area'),
110 messagesWrapper: E1('#chat-messages-wrapper'),
111 btnSubmit: E1('#chat-message-submit'),
112 inputSingle: E1('#chat-input-single'),
113 inputMulti: E1('#chat-input-multi'),
114 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
115 inputFile: E1('#chat-input-file'),
116 contentDiv: E1('div.content'),
117 configArea: E1('#chat-config'),
118 previewArea: E1('#chat-preview'),
119 previewContent: E1('#chat-preview-content'),
120 btnPreview: E1('#chat-preview-button')
 
 
 
121 },
122 me: F.user.name,
123 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
124 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
125 pageIsActive: 'visible'===document.visibilityState,
@@ -127,10 +150,23 @@
127 notificationBubbleColor: 'white',
128 totalMessageCount: 0, // total # of inbound messages
129 //! Number of messages to load for the history buttons
130 loadMessageCount: Math.abs(F.config.chat.initSize || 20),
131 ajaxInflight: 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
132 /** Gets (no args) or sets (1 arg) the current input text field value,
133 taking into account single- vs multi-line input. The getter returns
134 a string and the setter returns this object. */
135 inputValue: function(){
136 const e = this.inputElement();
@@ -157,19 +193,20 @@
157 this.e.inputLine.classList.remove('single-line');
158 }else{
159 this.e.inputCurrent = this.e.inputSingle;
160 this.e.inputLine.classList.add('single-line');
161 }
162 const m = this.e.messagesWrapper,
163 sTop = m.scrollTop,
164 mh1 = m.clientHeight;
165 D.addClass(old, 'hidden');
166 D.removeClass(this.e.inputCurrent, 'hidden');
167 const mh2 = m.clientHeight;
168 m.scrollTo(0, sTop + (mh1-mh2));
169 this.e.inputCurrent.value = old.value;
170 old.value = '';
 
171 return this;
172 },
173 /**
174 If passed true or no arguments, switches to multi-line mode
175 if currently in single-line mode. If passed false, switches
@@ -241,12 +278,15 @@
241 /* Injects DOM element e as a new row in the chat, at the oldest
242 end of the list if atEnd is truthy, else at the newest end of
243 the list. */
244 injectMessageElem: function f(e, atEnd){
245 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
246 holder = this.e.messagesWrapper,
247 prevMessage = this.e.newestMessage;
 
 
 
248 if(atEnd){
249 const fe = mip.nextElementSibling;
250 if(fe) mip.parentNode.insertBefore(e, fe);
251 else D.append(mip.parentNode, e);
252 }else{
@@ -337,22 +377,22 @@
337 <0 = top of the message list, >0 = bottom of the message list,
338 0 == the newest message (normally the same position as >1).
339 */
340 scrollMessagesTo: function(where){
341 if(where<0){
342 Chat.e.messagesWrapper.scrollTop = 0;
343 }else if(where>0){
344 Chat.e.messagesWrapper.scrollTop = Chat.e.messagesWrapper.scrollHeight;
345 }else if(Chat.e.newestMessage){
346 Chat.e.newestMessage.scrollIntoView(false);
347 }
348 },
349 toggleChatOnlyMode: function(){
350 return this.chatOnlyMode(!this.isChatOnlyMode());
351 },
352 messageIsInView: function(e){
353 return e ? overlapsElemView(e, this.e.messagesWrapper) : false;
354 },
355 settings:{
356 get: (k,dflt)=>F.storage.get(k,dflt),
357 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
358 set: (k,v)=>F.storage.set(k,v),
@@ -366,11 +406,13 @@
366 defaults:{
367 "images-inline": !!F.config.chat.imagesInline,
368 "edit-multiline": false,
369 "monospace-messages": false,
370 "chat-only-mode": false,
371 "audible-alert": true
 
 
372 }
373 },
374 /** Plays a new-message notification sound IF the audible-alert
375 setting is true, else this is a no-op. Returns this.
376 */
@@ -395,12 +437,117 @@
395 setNewMessageSound: function f(uri){
396 delete this.playNewMessageSound.audio;
397 this.playNewMessageSound.uri = uri;
398 this.settings.set('audible-alert', uri);
399 return this;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400 }
401 };
 
402 F.fetch.beforesend = ()=>cs.ajaxStart();
403 F.fetch.aftersend = ()=>cs.ajaxEnd();
404 cs.e.inputCurrent = cs.e.inputSingle;
405 /* Install default settings... */
406 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -415,10 +562,16 @@
415 tall vs wide. Can be toggled via settings popup. */
416 document.body.classList.add('my-messages-right');
417 }
418 if(cs.settings.getBool('monospace-messages',false)){
419 document.body.classList.add('monospace-messages');
 
 
 
 
 
 
420 }
421 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
422 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
423 cs.pageTitleOrig = cs.e.pageTitle.innerText;
424 const qs = (e)=>document.querySelector(e);
@@ -611,10 +764,37 @@
611 cs.pageIsActive = ('visible' === document.visibilityState);
612 if(cs.pageIsActive){
613 cs.e.pageTitle.innerText = cs.pageTitleOrig;
614 }
615 }, true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616 return cs;
617 })()/*Chat initialization*/;
618
619 /**
620 Custom widget type for rendering messages (one message per
@@ -658,21 +838,10 @@
658 d.getHours(),":",
659 (d.getMinutes()+100).toString().slice(1,3),
660 ' ', dowMap[d.getDay()]
661 ].join('');
662 };
663 /** Returns the local time string of Date object d, defaulting
664 to the current time. */
665 const localTimeString = function ff(d){
666 d || (d = new Date());
667 return [
668 d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
669 '-',pad2(d.getDate()),
670 ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
671 ':',pad2(d.getSeconds())
672 ].join('');
673 };
674 cf.prototype = {
675 scrollIntoView: function(){
676 this.e.content.scrollIntoView();
677 },
678 setMessage: function(m){
@@ -758,14 +927,14 @@
758 eXFrom.addEventListener('click', ()=>this.e.tab.click(), false);
759 }*/
760 return this;
761 },
762 /* Event handler for clicking .message-user elements to show their
763 timestamps. */
764 _handleLegendClicked: function f(ev){
765 if(!f.popup){
766 /* Timestamp popup widget */
767 f.popup = {
768 e: D.addClass(D.div(), 'chat-message-popup'),
769 refresh:function(){
770 const eMsg = this.$eMsg/*.message-widget element*/;
771 if(!eMsg) return;
@@ -830,18 +999,45 @@
830 y: 'a'
831 }), "User's Timeline"),
832 'target', '_blank'
833 );
834 D.append(toolbar2, timelineLink);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835 }
836 const tab = eMsg.querySelector('.message-widget-tab');
837 D.append(tab, this.e);
838 D.removeClass(this.e, 'hidden');
 
839 }/*refresh()*/,
840 hide: function(){
841 D.addClass(D.clearElement(this.e), 'hidden');
842 delete this.$eMsg;
 
 
843 },
844 show: function(tgtMsg){
845 if(tgtMsg === this.$eMsg){
846 this.hide();
847 return;
@@ -961,11 +1157,11 @@
961 */
962 Chat.submitMessage = function f(){
963 if(!f.spaces){
964 f.spaces = /\s+$/;
965 }
966 this.revealPreview(false);
967 const fd = new FormData();
968 var msg = this.inputValue().trim();
969 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
970 /* Cosmetic: trim whitespace from the ends of lines to try to
971 keep copy/paste from terminals, especially wide ones, from
@@ -1021,42 +1217,102 @@
1021 e.preventDefault();
1022 Chat.submitMessage();
1023 return false;
1024 });
1025
1026 /* Returns an almost-ISO8601 form of Date object d. */
1027 const iso8601ish = function(d){
1028 return d.toISOString()
1029 .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu');
1030 };
1031
1032 (function(){/*Set up #chat-settings-button */
1033 const settingsButton = document.querySelector('#chat-settings-button');
1034 const optionsMenu = E1('#chat-config-options');
1035 const cbToggle = function(ev){
1036 ev.preventDefault();
1037 ev.stopPropagation();
1038 if(Chat.e.configArea.classList.contains('hidden')){
1039 D.removeClass(Chat.e.configArea, 'hidden');
1040 D.addClass([Chat.e.messagesWrapper, Chat.e.previewArea], 'hidden');
1041 }else{
1042 D.addClass(Chat.e.configArea, 'hidden');
1043 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1044 }
1045 return false;
1046 };
1047 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1048 Chat.e.configArea.querySelector('button').addEventListener('click', cbToggle, false);
1049 /* Settings menu entries... */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1050 const settingsOps = [{
1051 label: "Multi-line input",
1052 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1053 persistentSetting: 'edit-multiline',
1054 callback: function(){
1055 Chat.inputToggleSingleMulti();
1056 }
1057 },{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1058 label: "Monospace message font",
1059 boolValue: ()=>document.body.classList.contains('monospace-messages'),
1060 persistentSetting: 'monospace-messages',
1061 callback: function(){
1062 document.body.classList.toggle('monospace-messages');
@@ -1066,57 +1322,44 @@
1066 boolValue: ()=>Chat.isChatOnlyMode(),
1067 persistentSetting: 'chat-only-mode',
1068 callback: function(){
1069 Chat.toggleChatOnlyMode();
1070 }
1071 },{
1072 label: "Left-align my posts",
1073 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1074 callback: function f(){
1075 document.body.classList.toggle('my-messages-right');
1076 }
1077 },{
1078 label: "Images inline",
1079 boolValue: ()=>Chat.settings.getBool('images-inline'),
1080 callback: function(){
1081 const v = Chat.settings.toggle('images-inline');
1082 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1083 }
1084 }];
1085
1086 /** Set up selection list of notification sounds. */
1087 if(1){
1088 settingsOps.selectSound = D.addClass(D.div(), 'menu-entry');
1089 const selectSound = D.select();
1090 D.append(settingsOps.selectSound,
1091 D.append(D.span(),"Audio alert"),
1092 selectSound);
1093 D.option(selectSound, "", "(no audio)");
1094 const firstSoundIndex = selectSound.options.length;
1095 F.config.chat.alerts.forEach(function(a){
1096 D.option(selectSound, a);
1097 });
1098 if(true===Chat.settings.getBool('audible-alert')){
 
 
1099 selectSound.selectedIndex = firstSoundIndex;
1100 }else{
1101 selectSound.value = Chat.settings.get('audible-alert','');
1102 if(selectSound.selectedIndex<0){
1103 /*Missing file - removed after this setting was applied. Fall back
1104 to the first sound in the list. */
1105 selectSound.selectedIndex = firstSoundIndex;
1106 }
1107 }
1108 selectSound.addEventListener('change',function(){
1109 const v = this.value;
1110 Chat.setNewMessageSound(v);
1111 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1112 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1113 }, false);
1114 Chat.setNewMessageSound(selectSound.value);
 
 
 
 
 
 
 
 
 
 
1115 }/*audio notification config*/
1116 /**
1117 Build list of options...
1118 */
1119 settingsOps.forEach(function f(op){
1120 const line = D.addClass(D.div(), 'menu-entry');
1121 const btn = D.append(
1122 D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
@@ -1125,62 +1368,52 @@
1125 op.callback(ev);
1126 if(op.persistentSetting){
1127 Chat.settings.set(op.persistentSetting, op.boolValue());
1128 }
1129 };
1130 if(op.hasOwnProperty('boolValue')){
 
 
 
1131 if(undefined === f.$id) f.$id = 0;
1132 ++f.$id;
1133 const check = D.attr(D.checkbox(1, op.boolValue()),
1134 'aria-label', op.label);
 
1135 const id = 'cfgopt'+f.$id;
1136 if(op.boolValue()) check.checked = true;
1137 D.attr(check, 'id', id);
1138 D.attr(btn, 'for', id);
1139 D.append(line, check);
1140 check.addEventListener('change', callback);
 
1141 }else{
1142 line.addEventListener('click', callback);
 
1143 }
1144 D.append(line, btn);
1145 D.append(optionsMenu, line);
1146 });
1147 if(settingsOps.selectSound){
1148 D.append(optionsMenu, settingsOps.selectSound);
1149 }
1150 //settingsButton.click()/*for for development*/;
1151 })()/*#chat-settings-button setup*/;
1152
1153 (function(){/*set up message preview*/
1154 const btnPreview = Chat.e.btnPreview;
1155 Chat.setPreviewText = function(t){
1156 this.revealPreview(true).e.previewContent.innerHTML = t;
1157 this.e.previewArea.querySelectorAll('a').forEach(addAnchorTargetBlank);
 
1158 this.e.inputCurrent.focus();
1159 };
1160 /**
1161 Reveals preview area if showIt is true, else hides it.
1162 This also shows/hides other elements, "as appropriate."
1163 */
1164 Chat.revealPreview = function(showIt){
1165 if(showIt){
1166 D.removeClass(Chat.e.previewArea, 'hidden');
1167 D.addClass([Chat.e.messagesWrapper, Chat.e.configArea],
1168 'hidden');
1169 }else{
1170 D.addClass([Chat.e.configArea, Chat.e.previewArea], 'hidden');
1171 D.removeClass(Chat.e.messagesWrapper, 'hidden');
1172 }
1173 return this;
1174 };
1175 Chat.e.previewArea.querySelector('#chat-preview-close').
1176 addEventListener('click', ()=>Chat.revealPreview(false), false);
1177 let previewPending = false;
1178 const elemsToEnable = [
1179 btnPreview, Chat.e.btnSubmit,
1180 Chat.e.inputSingle, Chat.e.inputMulti];
1181 Chat.disableDuringAjax.push(btnPreview);
1182 const submit = function(ev){
1183 ev.preventDefault();
1184 ev.stopPropagation();
1185 if(previewPending) return false;
1186 const txt = Chat.e.inputCurrent.value;
@@ -1209,12 +1442,12 @@
1209 previewPending = true;
1210 Chat.setPreviewText("Loading preview...");
1211 },
1212 aftersend:function(){
1213 previewPending = false;
1214 D.enable(elemsToEnable);
1215 Chat.ajaxEnd();
 
1216 }
1217 });
1218 return false;
1219 };
1220 btnPreview.addEventListener('click', submit, false);
@@ -1231,10 +1464,18 @@
1231 should only be true when loading older messages. */
1232 f.processPost = function(m,atEnd){
1233 ++Chat.totalMessageCount;
1234 if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
1235 if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
 
 
 
 
 
 
 
 
1236 if( m.mdel ){
1237 /* A record deletion notice. */
1238 Chat.deleteMessageElem(m.mdel);
1239 return;
1240 }
@@ -1247,10 +1488,11 @@
1247 Chat._gotServerError = m;
1248 }
1249 }/*processPost()*/;
1250 }/*end static init*/
1251 jx.msgs.forEach((m)=>f.processPost(m,atEnd));
 
1252 if('visible'===document.visibilityState){
1253 if(Chat.changesSincePageHidden){
1254 Chat.changesSincePageHidden = 0;
1255 Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
1256 }
@@ -1273,14 +1515,14 @@
1273 D.fieldset(loadLegend), "id", "load-msg-toolbar"
1274 );
1275 Chat.disableDuringAjax.push(toolbar);
1276 /* Loads the next n oldest messages, or all previous history if n is negative. */
1277 const loadOldMessages = function(n){
1278 Chat.e.messagesWrapper.classList.add('loading');
1279 Chat._isBatchLoading = true;
1280 const scrollHt = Chat.e.messagesWrapper.scrollHeight,
1281 scrollTop = Chat.e.messagesWrapper.scrollTop;
1282 F.fetch("chat-poll",{
1283 urlParams:{
1284 before: Chat.mnMsg,
1285 n: n
1286 },
@@ -1291,10 +1533,11 @@
1291 },
1292 onload:function(x){
1293 let gotMessages = x.msgs.length;
1294 newcontent(x,true);
1295 Chat._isBatchLoading = false;
 
1296 if(Chat._gotServerError){
1297 Chat._gotServerError = false;
1298 return;
1299 }
1300 if(n<0/*we asked for all history*/
@@ -1314,17 +1557,17 @@
1314 }
1315 if(gotMessages > 0){
1316 F.toast.message("Loaded "+gotMessages+" older messages.");
1317 /* Return scroll position to where it was when the history load
1318 was requested, per user request */
1319 Chat.e.messagesWrapper.scrollTo(
1320 0, Chat.e.messagesWrapper.scrollHeight - scrollHt + scrollTop
1321 );
1322 }
1323 },
1324 aftersend:function(){
1325 Chat.e.messagesWrapper.classList.remove('loading');
1326 Chat.ajaxEnd();
1327 }
1328 });
1329 };
1330 const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1333,19 +1576,19 @@
1333 D.append(wrapper, btn);
1334 btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
1335 btn = D.button("All previous messages");
1336 D.append(wrapper, btn);
1337 btn.addEventListener('click',()=>loadOldMessages(-1));
1338 D.append(Chat.e.messagesWrapper, toolbar);
1339 toolbar.disabled = true /*will be enabled when msg load finishes */;
1340 })()/*end history loading widget setup*/;
1341
1342 const afterFetch = function f(){
1343 if(true===f.isFirstCall){
1344 f.isFirstCall = false;
1345 Chat.ajaxEnd();
1346 Chat.e.messagesWrapper.classList.remove('loading');
1347 setTimeout(function(){
1348 Chat.scrollMessagesTo(1);
1349 }, 250);
1350 }
1351 if(Chat._gotServerError && Chat.intervalTimer){
@@ -1363,11 +1606,11 @@
1363 f.running = true;
1364 Chat._isBatchLoading = f.isFirstCall;
1365 if(true===f.isFirstCall){
1366 f.isFirstCall = false;
1367 Chat.ajaxStart();
1368 Chat.e.messagesWrapper.classList.add('loading');
1369 }
1370 F.fetch("chat-poll",{
1371 timeout: 420 * 1000/*FIXME: get the value from the server*/,
1372 urlParams:{
1373 name: Chat.mxMsg
@@ -1384,11 +1627,14 @@
1384 resumed, and reportError() produces a loud error message. */
1385 afterFetch();
1386 },
1387 onload:function(y){
1388 newcontent(y);
1389 Chat._isBatchLoading = false;
 
 
 
1390 afterFetch();
1391 }
1392 });
1393 };
1394 poll.isFirstCall = true;
@@ -1395,7 +1641,15 @@
1395 Chat._gotServerError = poll.running = false;
1396 if( window.fossil.config.chat.fromcli ){
1397 Chat.chatOnlyMode(true);
1398 }
1399 Chat.intervalTimer = setInterval(poll, 1000);
 
 
 
 
 
 
 
 
1400 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
1401 })();
1402
--- src/chat.js
+++ src/chat.js
@@ -34,10 +34,30 @@
34 else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true;
35 return false;
36 };
37
38 const addAnchorTargetBlank = (e)=>D.attr(e, 'target','_blank');
39
40 /**
41 Returns an almost-ISO8601 form of Date object d.
42 */
43 const iso8601ish = function(d){
44 return d.toISOString()
45 .replace('T',' ').replace(/\.\d+/,'')
46 .replace('Z', ' zulu');
47 };
48 /** Returns the local time string of Date object d, defaulting
49 to the current time. */
50 const localTimeString = function ff(d){
51 d || (d = new Date());
52 return [
53 d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/),
54 '-',pad2(d.getDate()),
55 ' ',pad2(d.getHours()),':',pad2(d.getMinutes()),
56 ':',pad2(d.getSeconds())
57 ].join('');
58 };
59
60 (function(){
61 let dbg = document.querySelector('#debugMsg');
62 if(dbg){
63 /* This can inadvertently influence our flexbox layouts, so move
@@ -74,11 +94,11 @@
94 ht = wh - extra;
95 }
96 f.contentArea.style.height =
97 f.contentArea.style.maxHeight = [
98 "calc(", (ht>=100 ? ht : 100), "px",
99 " - 0.75em"/*fudge value*/,")"
100 /* ^^^^ hypothetically not needed, but both Chrome/FF on
101 Linux will force scrollbars on the body if this value is
102 too small (<0.75em in my tests). */
103 ].join('');
104 if(false){
@@ -105,21 +125,24 @@
125 pageTitle: E1('head title'),
126 loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
127 inputWrapper: E1("#chat-input-area"),
128 inputLine: E1('#chat-input-line'),
129 fileSelectWrapper: E1('#chat-input-file-area'),
130 viewMessages: E1('#chat-messages-wrapper'),
131 btnSubmit: E1('#chat-message-submit'),
132 inputSingle: E1('#chat-input-single'),
133 inputMulti: E1('#chat-input-multi'),
134 inputCurrent: undefined/*one of inputSingle or inputMulti*/,
135 inputFile: E1('#chat-input-file'),
136 contentDiv: E1('div.content'),
137 viewConfig: E1('#chat-config'),
138 viewPreview: E1('#chat-preview'),
139 previewContent: E1('#chat-preview-content'),
140 btnPreview: E1('#chat-preview-button'),
141 views: document.querySelectorAll('.chat-view'),
142 activeUserListWrapper: E1('#chat-user-list-wrapper'),
143 activeUserList: E1('#chat-user-list')
144 },
145 me: F.user.name,
146 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
147 mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
148 pageIsActive: 'visible'===document.visibilityState,
@@ -127,10 +150,23 @@
150 notificationBubbleColor: 'white',
151 totalMessageCount: 0, // total # of inbound messages
152 //! Number of messages to load for the history buttons
153 loadMessageCount: Math.abs(F.config.chat.initSize || 20),
154 ajaxInflight: 0,
155 usersLastSeen:{
156 /* Map of user names to their most recent message time
157 (JS Date object). Only messages received by the chat client
158 are considered. */
159 /* Reminder: to convert a Julian time J to JS:
160 new Date((J - 2440587.5) * 86400000) */
161 },
162 filterState:{
163 activeUser: undefined,
164 match: function(uname){
165 return this.activeUser===uname || !this.activeUser;
166 }
167 },
168 /** Gets (no args) or sets (1 arg) the current input text field value,
169 taking into account single- vs multi-line input. The getter returns
170 a string and the setter returns this object. */
171 inputValue: function(){
172 const e = this.inputElement();
@@ -157,19 +193,20 @@
193 this.e.inputLine.classList.remove('single-line');
194 }else{
195 this.e.inputCurrent = this.e.inputSingle;
196 this.e.inputLine.classList.add('single-line');
197 }
198 const m = this.e.viewMessages,
199 sTop = m.scrollTop,
200 mh1 = m.clientHeight;
201 D.addClass(old, 'hidden');
202 D.removeClass(this.e.inputCurrent, 'hidden');
203 const mh2 = m.clientHeight;
204 m.scrollTo(0, sTop + (mh1-mh2));
205 this.e.inputCurrent.value = old.value;
206 old.value = '';
207 this.animate(this.e.inputCurrent, "anim-flip-v");
208 return this;
209 },
210 /**
211 If passed true or no arguments, switches to multi-line mode
212 if currently in single-line mode. If passed false, switches
@@ -241,12 +278,15 @@
278 /* Injects DOM element e as a new row in the chat, at the oldest
279 end of the list if atEnd is truthy, else at the newest end of
280 the list. */
281 injectMessageElem: function f(e, atEnd){
282 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
283 holder = this.e.viewMessages,
284 prevMessage = this.e.newestMessage;
285 if(!this.filterState.match(e.dataset.xfrom)){
286 e.classList.add('hidden');
287 }
288 if(atEnd){
289 const fe = mip.nextElementSibling;
290 if(fe) mip.parentNode.insertBefore(e, fe);
291 else D.append(mip.parentNode, e);
292 }else{
@@ -337,22 +377,22 @@
377 <0 = top of the message list, >0 = bottom of the message list,
378 0 == the newest message (normally the same position as >1).
379 */
380 scrollMessagesTo: function(where){
381 if(where<0){
382 Chat.e.viewMessages.scrollTop = 0;
383 }else if(where>0){
384 Chat.e.viewMessages.scrollTop = Chat.e.viewMessages.scrollHeight;
385 }else if(Chat.e.newestMessage){
386 Chat.e.newestMessage.scrollIntoView(false);
387 }
388 },
389 toggleChatOnlyMode: function(){
390 return this.chatOnlyMode(!this.isChatOnlyMode());
391 },
392 messageIsInView: function(e){
393 return e ? overlapsElemView(e, this.e.viewMessages) : false;
394 },
395 settings:{
396 get: (k,dflt)=>F.storage.get(k,dflt),
397 getBool: (k,dflt)=>F.storage.getBool(k,dflt),
398 set: (k,v)=>F.storage.set(k,v),
@@ -366,11 +406,13 @@
406 defaults:{
407 "images-inline": !!F.config.chat.imagesInline,
408 "edit-multiline": false,
409 "monospace-messages": false,
410 "chat-only-mode": false,
411 "audible-alert": true,
412 "active-user-list": false,
413 "active-user-list-timestamps": false
414 }
415 },
416 /** Plays a new-message notification sound IF the audible-alert
417 setting is true, else this is a no-op. Returns this.
418 */
@@ -395,12 +437,117 @@
437 setNewMessageSound: function f(uri){
438 delete this.playNewMessageSound.audio;
439 this.playNewMessageSound.uri = uri;
440 this.settings.set('audible-alert', uri);
441 return this;
442 },
443 /**
444 Expects e to be one of the elements in this.e.views.
445 The 'hidden' class is removed from e and added to
446 all other elements in that list. Returns e.
447 */
448 setCurrentView: function(e){
449 if(e===this.e.currentView){
450 return e;
451 }
452 this.e.views.forEach(function(E){
453 if(e!==E) D.addClass(E,'hidden');
454 });
455 this.e.currentView = D.removeClass(e,'hidden');
456 this.animate(this.e.currentView, 'anim-fade-in-fast');
457 return this.e.currentView;
458 },
459 /**
460 Updates the "active user list" view if we are not currently
461 batch-loading messages and if the active user list UI element
462 is active.
463 */
464 updateActiveUserList: function callee(){
465 if(this._isBatchLoading
466 || this.e.activeUserListWrapper.classList.contains('hidden')){
467 return this;
468 }else if(!callee.sortUsersSeen){
469 /** Array.sort() callback. Expects an array of user names and
470 sorts them in last-received message order (newest first). */
471 const self = this;
472 callee.sortUsersSeen = function(l,r){
473 l = self.usersLastSeen[l];
474 r = self.usersLastSeen[r];
475 if(l && r) return r - l;
476 else if(l) return -1;
477 else if(r) return 1;
478 else return 0;
479 };
480 callee.addUserElem = function(u){
481 const uSpan = D.addClass(D.span(), 'chat-user');
482 const uDate = self.usersLastSeen[u];
483 if(self.filterState.activeUser===u){
484 uSpan.classList.add('selected');
485 }
486 uSpan.dataset.uname = u;
487 D.append(uSpan, u, "\n",
488 D.append(
489 D.addClass(D.span(),'timestamp'),
490 localTimeString(uDate)//.substr(5/*chop off year*/)
491 ));
492 if(uDate.$uColor){
493 uSpan.style.backgroundColor = uDate.$uColor;
494 }
495 D.append(self.e.activeUserList, uSpan);
496 };
497 }
498 //D.clearElement(this.e.activeUserList);
499 D.remove(this.e.activeUserList.querySelectorAll('.chat-user'));
500 Object.keys(this.usersLastSeen).sort(
501 callee.sortUsersSeen
502 ).forEach(callee.addUserElem);
503 return this;
504 },
505 /**
506 Applies user name filter to all current messages, or clears
507 the filter if uname is falsy.
508 */
509 setUserFilter: function(uname){
510 this.filterState.activeUser = uname;
511 const mw = this.e.viewMessages.querySelectorAll('.message-widget');
512 const self = this;
513 let eLast;
514 if(!uname){
515 D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'),
516 'hidden');
517 }else{
518 mw.forEach(function(w){
519 if(self.filterState.match(w.dataset.xfrom)){
520 w.classList.remove('hidden');
521 eLast = w;
522 }else{
523 w.classList.add('hidden');
524 }
525 });
526 }
527 if(eLast) eLast.scrollIntoView(false);
528 else this.scrollMessagesTo(1);
529 cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
530 e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
531 });
532 return this;
533 },
534
535 /**
536 If animations are enabled, passes its arguments
537 to D.addClassBriefly(), else this is a no-op.
538 If cb is a function, it is called after the
539 CSS class is removed. Returns this object;
540 */
541 animate: function f(e,a,cb){
542 if(!f.$disabled){
543 D.addClassBriefly(e, a, 0, cb);
544 }
545 return this;
546 }
547 };
548 cs.animate.$disabled = true;
549 F.fetch.beforesend = ()=>cs.ajaxStart();
550 F.fetch.aftersend = ()=>cs.ajaxEnd();
551 cs.e.inputCurrent = cs.e.inputSingle;
552 /* Install default settings... */
553 Object.keys(cs.settings.defaults).forEach(function(k){
@@ -415,10 +562,16 @@
562 tall vs wide. Can be toggled via settings popup. */
563 document.body.classList.add('my-messages-right');
564 }
565 if(cs.settings.getBool('monospace-messages',false)){
566 document.body.classList.add('monospace-messages');
567 }
568 if(cs.settings.getBool('active-user-list',false)){
569 cs.e.activeUserListWrapper.classList.remove('hidden');
570 }
571 if(cs.settings.getBool('active-user-list-timestamps',false)){
572 cs.e.activeUserList.classList.add('timestamps');
573 }
574 cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false));
575 cs.chatOnlyMode(cs.settings.getBool('chat-only-mode'));
576 cs.pageTitleOrig = cs.e.pageTitle.innerText;
577 const qs = (e)=>document.querySelector(e);
@@ -611,10 +764,37 @@
764 cs.pageIsActive = ('visible' === document.visibilityState);
765 if(cs.pageIsActive){
766 cs.e.pageTitle.innerText = cs.pageTitleOrig;
767 }
768 }, true);
769 cs.setCurrentView(cs.e.viewMessages);
770
771 cs.e.activeUserList.addEventListener('click', function f(ev){
772 /* Filter messages on a user clicked in activeUserList */
773 ev.stopPropagation();
774 ev.preventDefault();
775 let eUser = ev.target;
776 while(eUser!==this && !eUser.classList.contains('chat-user')){
777 eUser = eUser.parentNode;
778 }
779 if(eUser==this || !eUser) return false;
780 const uname = eUser.dataset.uname;
781 let eLast;
782 cs.setCurrentView(cs.e.viewMessages);
783 if(eUser.classList.contains('selected')){
784 /* If curently selected, toggle filter off */
785 eUser.classList.remove('selected');
786 cs.setUserFilter(false);
787 delete f.$eSelected;
788 }else{
789 if(f.$eSelected) f.$eSelected.classList.remove('selected');
790 f.$eSelected = eUser;
791 eUser.classList.add('selected');
792 cs.setUserFilter(uname);
793 }
794 return false;
795 }, false);
796 return cs;
797 })()/*Chat initialization*/;
798
799 /**
800 Custom widget type for rendering messages (one message per
@@ -658,21 +838,10 @@
838 d.getHours(),":",
839 (d.getMinutes()+100).toString().slice(1,3),
840 ' ', dowMap[d.getDay()]
841 ].join('');
842 };
 
 
 
 
 
 
 
 
 
 
 
843 cf.prototype = {
844 scrollIntoView: function(){
845 this.e.content.scrollIntoView();
846 },
847 setMessage: function(m){
@@ -758,14 +927,14 @@
927 eXFrom.addEventListener('click', ()=>this.e.tab.click(), false);
928 }*/
929 return this;
930 },
931 /* Event handler for clicking .message-user elements to show their
932 timestamps and a set of actions. */
933 _handleLegendClicked: function f(ev){
934 if(!f.popup){
935 /* "Popup" widget */
936 f.popup = {
937 e: D.addClass(D.div(), 'chat-message-popup'),
938 refresh:function(){
939 const eMsg = this.$eMsg/*.message-widget element*/;
940 if(!eMsg) return;
@@ -830,18 +999,45 @@
999 y: 'a'
1000 }), "User's Timeline"),
1001 'target', '_blank'
1002 );
1003 D.append(toolbar2, timelineLink);
1004 if(Chat.filterState.activeUser &&
1005 Chat.filterState.match(eMsg.dataset.xfrom)){
1006 /* Add a button to clear user filter and jump to
1007 this message in its original context. */
1008 D.append(
1009 this.e,
1010 D.append(
1011 D.addClass(D.div(), 'toolbar'),
1012 D.button(
1013 "Message in context",
1014 function(){
1015 self.hide();
1016 Chat.setUserFilter(false);
1017 eMsg.scrollIntoView(false);
1018 Chat.animate(
1019 eMsg.firstElementChild, 'anim-flip-h'
1020 //eMsg.firstElementChild, 'anim-flip-v'
1021 //eMsg.childNodes, 'anim-rotate-360'
1022 //eMsg.childNodes, 'anim-flip-v'
1023 //eMsg, 'anim-flip-v'
1024 );
1025 })
1026 )
1027 );
1028 }/*jump-to button*/
1029 }
1030 const tab = eMsg.querySelector('.message-widget-tab');
1031 D.append(tab, this.e);
1032 D.removeClass(this.e, 'hidden');
1033 Chat.animate(this.e, 'anim-fade-in-fast');
1034 }/*refresh()*/,
1035 hide: function(){
 
1036 delete this.$eMsg;
1037 D.addClass(this.e, 'hidden');
1038 D.clearElement(this.e);
1039 },
1040 show: function(tgtMsg){
1041 if(tgtMsg === this.$eMsg){
1042 this.hide();
1043 return;
@@ -961,11 +1157,11 @@
1157 */
1158 Chat.submitMessage = function f(){
1159 if(!f.spaces){
1160 f.spaces = /\s+$/;
1161 }
1162 this.setCurrentView(this.e.viewMessages);
1163 const fd = new FormData();
1164 var msg = this.inputValue().trim();
1165 if(msg && (msg.indexOf('\n')>0 || f.spaces.test(msg))){
1166 /* Cosmetic: trim whitespace from the ends of lines to try to
1167 keep copy/paste from terminals, especially wide ones, from
@@ -1021,42 +1217,102 @@
1217 e.preventDefault();
1218 Chat.submitMessage();
1219 return false;
1220 });
1221
 
 
 
 
 
 
1222 (function(){/*Set up #chat-settings-button */
1223 const settingsButton = document.querySelector('#chat-settings-button');
1224 const optionsMenu = E1('#chat-config-options');
1225 const cbToggle = function(ev){
1226 ev.preventDefault();
1227 ev.stopPropagation();
1228 Chat.setCurrentView(Chat.e.currentView===Chat.e.viewConfig
1229 ? Chat.e.viewMessages : Chat.e.viewConfig);
 
 
 
 
 
1230 return false;
1231 };
1232 D.attr(settingsButton, 'role', 'button').addEventListener('click', cbToggle, false);
1233 Chat.e.viewConfig.querySelector('button').addEventListener('click', cbToggle, false);
1234
1235 /** Internal acrobatics to allow certain settings toggles to access
1236 related toggles. */
1237 const namedOptions = {
1238 activeUsers:{
1239 label: "Show active users list",
1240 boolValue: ()=>!Chat.e.activeUserListWrapper.classList.contains('hidden'),
1241 persistentSetting: 'active-user-list',
1242 callback: function(){
1243 D.toggleClass(Chat.e.activeUserListWrapper,'hidden');
1244 D.removeClass(Chat.e.activeUserListWrapper, 'collapsed');
1245 if(Chat.e.activeUserListWrapper.classList.contains('hidden')){
1246 /* When hiding this element, undo all filtering */
1247 Chat.setUserFilter(false);
1248 /*Ideally we'd scroll the final message into view
1249 now, but because viewMessages is currently hidden behind
1250 viewConfig, scrolling is a no-op. */
1251 Chat.scrollMessagesTo(1);
1252 }else{
1253 Chat.updateActiveUserList();
1254 Chat.animate(Chat.e.activeUserListWrapper, 'anim-flip-v');
1255 }
1256 }
1257 }
1258 };
1259 if(1){
1260 /* Per user request, toggle the list of users on and off if the
1261 legend element is tapped. */
1262 const optAu = namedOptions.activeUsers;
1263 optAu.theLegend = Chat.e.activeUserListWrapper.firstElementChild/*LEGEND*/;
1264 optAu.theList = optAu.theLegend.nextElementSibling/*user list container*/;
1265 optAu.theLegend.addEventListener('click',function(){
1266 D.toggleClass(Chat.e.activeUserListWrapper, 'collapsed');
1267 if(!Chat.e.activeUserListWrapper.classList.contains('collapsed')){
1268 Chat.animate(optAu.theList,'anim-flip-v');
1269 }
1270 }, false);
1271 }/*namedOptions.activeUsers additional setup*/
1272 /* Settings menu entries... Remember that they will be rendered in
1273 reverse order and the most frequently-needed ones "should"
1274 (arguably) be closer to the start of this list so that they
1275 will be rendered within easier reach of the settings button. */
1276 const settingsOps = [{
1277 label: "Multi-line input",
1278 boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti,
1279 persistentSetting: 'edit-multiline',
1280 callback: function(){
1281 Chat.inputToggleSingleMulti();
1282 }
1283 },{
1284 label: "Left-align my posts",
1285 boolValue: ()=>!document.body.classList.contains('my-messages-right'),
1286 callback: function f(){
1287 document.body.classList.toggle('my-messages-right');
1288 }
1289 },{
1290 label: "Show images inline",
1291 boolValue: ()=>Chat.settings.getBool('images-inline'),
1292 callback: function(){
1293 const v = Chat.settings.toggle('images-inline');
1294 F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+".");
1295 }
1296 },{
1297 label: "Timestamps in active users list",
1298 boolValue: ()=>Chat.e.activeUserList.classList.contains('timestamps'),
1299 persistentSetting: 'active-user-list-timestamps',
1300 callback: function(){
1301 D.toggleClass(Chat.e.activeUserList,'timestamps');
1302 /* If the timestamp option is activated but
1303 namedOptions.activeUsers is not currently checked then
1304 toggle that option on as well. */
1305 if(Chat.e.activeUserList.classList.contains('timestamps')
1306 && !namedOptions.activeUsers.boolValue()){
1307 namedOptions.activeUsers.checkbox.checked = true;
1308 namedOptions.activeUsers.callback();
1309 Chat.settings.set(namedOptions.activeUsers.persistentSetting, true);
1310 }
1311 }
1312 },
1313 namedOptions.activeUsers,{
1314 label: "Monospace message font",
1315 boolValue: ()=>document.body.classList.contains('monospace-messages'),
1316 persistentSetting: 'monospace-messages',
1317 callback: function(){
1318 document.body.classList.toggle('monospace-messages');
@@ -1066,57 +1322,44 @@
1322 boolValue: ()=>Chat.isChatOnlyMode(),
1323 persistentSetting: 'chat-only-mode',
1324 callback: function(){
1325 Chat.toggleChatOnlyMode();
1326 }
 
 
 
 
 
 
 
 
 
 
 
 
 
1327 }];
1328
1329 /** Set up selection list of notification sounds. */
1330 if(1){
 
1331 const selectSound = D.select();
 
 
 
1332 D.option(selectSound, "", "(no audio)");
1333 const firstSoundIndex = selectSound.options.length;
1334 F.config.chat.alerts.forEach((a)=>D.option(selectSound, a));
 
 
1335 if(true===Chat.settings.getBool('audible-alert')){
1336 /* This setting used to be a plain bool. If we encounter
1337 such a setting, take the first sound in the list. */
1338 selectSound.selectedIndex = firstSoundIndex;
1339 }else{
1340 selectSound.value = Chat.settings.get('audible-alert','');
1341 if(selectSound.selectedIndex<0){
1342 /* Missing file - removed after this setting was
1343 applied. Fall back to the first sound in the list. */
1344 selectSound.selectedIndex = firstSoundIndex;
1345 }
1346 }
 
 
 
 
 
 
1347 Chat.setNewMessageSound(selectSound.value);
1348 settingsOps.push({
1349 label: "Audio alert",
1350 select: selectSound,
1351 callback: function(ev){
1352 const v = ev.target.value;
1353 Chat.setNewMessageSound(v);
1354 F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+".");
1355 if(v) setTimeout(()=>Chat.playNewMessageSound(), 0);
1356 }
1357 });
1358 }/*audio notification config*/
1359 /**
1360 Build UI for config options...
1361 */
1362 settingsOps.forEach(function f(op){
1363 const line = D.addClass(D.div(), 'menu-entry');
1364 const btn = D.append(
1365 D.addClass(D.label(), 'cbutton'/*bootstrap skin hijacks 'button'*/),
@@ -1125,62 +1368,52 @@
1368 op.callback(ev);
1369 if(op.persistentSetting){
1370 Chat.settings.set(op.persistentSetting, op.boolValue());
1371 }
1372 };
1373 if(op.hasOwnProperty('select')){
1374 D.append(line, btn, op.select);
1375 op.select.addEventListener('change', callback, false);
1376 }else if(op.hasOwnProperty('boolValue')){
1377 if(undefined === f.$id) f.$id = 0;
1378 ++f.$id;
1379 const check = op.checkbox
1380 = D.attr(D.checkbox(1, op.boolValue()),
1381 'aria-label', op.label);
1382 const id = 'cfgopt'+f.$id;
1383 if(op.boolValue()) check.checked = true;
1384 D.attr(check, 'id', id);
1385 D.attr(btn, 'for', id);
1386 D.append(line, check);
1387 check.addEventListener('change', callback);
1388 D.append(line, btn);
1389 }else{
1390 line.addEventListener('click', callback);
1391 D.append(line, btn);
1392 }
 
1393 D.append(optionsMenu, line);
1394 });
1395 if(0 && settingsOps.selectSound){
1396 D.append(optionsMenu, settingsOps.selectSound);
1397 }
1398 //settingsButton.click()/*for for development*/;
1399 })()/*#chat-settings-button setup*/;
1400
1401 (function(){/*set up message preview*/
1402 const btnPreview = Chat.e.btnPreview;
1403 Chat.setPreviewText = function(t){
1404 this.setCurrentView(this.e.viewPreview);
1405 this.e.previewContent.innerHTML = t;
1406 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
1407 this.e.inputCurrent.focus();
1408 };
1409 Chat.e.viewPreview.querySelector('#chat-preview-close').
1410 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1411 let previewPending = false;
1412 const elemsToEnable = [
1413 btnPreview, Chat.e.btnSubmit,
1414 Chat.e.inputSingle, Chat.e.inputMulti];
 
1415 const submit = function(ev){
1416 ev.preventDefault();
1417 ev.stopPropagation();
1418 if(previewPending) return false;
1419 const txt = Chat.e.inputCurrent.value;
@@ -1209,12 +1442,12 @@
1442 previewPending = true;
1443 Chat.setPreviewText("Loading preview...");
1444 },
1445 aftersend:function(){
1446 previewPending = false;
 
1447 Chat.ajaxEnd();
1448 D.enable(elemsToEnable);
1449 }
1450 });
1451 return false;
1452 };
1453 btnPreview.addEventListener('click', submit, false);
@@ -1231,10 +1464,18 @@
1464 should only be true when loading older messages. */
1465 f.processPost = function(m,atEnd){
1466 ++Chat.totalMessageCount;
1467 if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid;
1468 if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid;
1469 if(m.xfrom && m.mtime){
1470 const d = new Date(m.mtime);
1471 const uls = Chat.usersLastSeen[m.xfrom];
1472 if(!uls || uls<d){
1473 d.$uColor = m.uclr;
1474 Chat.usersLastSeen[m.xfrom] = d;
1475 }
1476 }
1477 if( m.mdel ){
1478 /* A record deletion notice. */
1479 Chat.deleteMessageElem(m.mdel);
1480 return;
1481 }
@@ -1247,10 +1488,11 @@
1488 Chat._gotServerError = m;
1489 }
1490 }/*processPost()*/;
1491 }/*end static init*/
1492 jx.msgs.forEach((m)=>f.processPost(m,atEnd));
1493 Chat.updateActiveUserList();
1494 if('visible'===document.visibilityState){
1495 if(Chat.changesSincePageHidden){
1496 Chat.changesSincePageHidden = 0;
1497 Chat.e.pageTitle.innerText = Chat.pageTitleOrig;
1498 }
@@ -1273,14 +1515,14 @@
1515 D.fieldset(loadLegend), "id", "load-msg-toolbar"
1516 );
1517 Chat.disableDuringAjax.push(toolbar);
1518 /* Loads the next n oldest messages, or all previous history if n is negative. */
1519 const loadOldMessages = function(n){
1520 Chat.e.viewMessages.classList.add('loading');
1521 Chat._isBatchLoading = true;
1522 const scrollHt = Chat.e.viewMessages.scrollHeight,
1523 scrollTop = Chat.e.viewMessages.scrollTop;
1524 F.fetch("chat-poll",{
1525 urlParams:{
1526 before: Chat.mnMsg,
1527 n: n
1528 },
@@ -1291,10 +1533,11 @@
1533 },
1534 onload:function(x){
1535 let gotMessages = x.msgs.length;
1536 newcontent(x,true);
1537 Chat._isBatchLoading = false;
1538 Chat.updateActiveUserList();
1539 if(Chat._gotServerError){
1540 Chat._gotServerError = false;
1541 return;
1542 }
1543 if(n<0/*we asked for all history*/
@@ -1314,17 +1557,17 @@
1557 }
1558 if(gotMessages > 0){
1559 F.toast.message("Loaded "+gotMessages+" older messages.");
1560 /* Return scroll position to where it was when the history load
1561 was requested, per user request */
1562 Chat.e.viewMessages.scrollTo(
1563 0, Chat.e.viewMessages.scrollHeight - scrollHt + scrollTop
1564 );
1565 }
1566 },
1567 aftersend:function(){
1568 Chat.e.viewMessages.classList.remove('loading');
1569 Chat.ajaxEnd();
1570 }
1571 });
1572 };
1573 const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */;
@@ -1333,19 +1576,19 @@
1576 D.append(wrapper, btn);
1577 btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount));
1578 btn = D.button("All previous messages");
1579 D.append(wrapper, btn);
1580 btn.addEventListener('click',()=>loadOldMessages(-1));
1581 D.append(Chat.e.viewMessages, toolbar);
1582 toolbar.disabled = true /*will be enabled when msg load finishes */;
1583 })()/*end history loading widget setup*/;
1584
1585 const afterFetch = function f(){
1586 if(true===f.isFirstCall){
1587 f.isFirstCall = false;
1588 Chat.ajaxEnd();
1589 Chat.e.viewMessages.classList.remove('loading');
1590 setTimeout(function(){
1591 Chat.scrollMessagesTo(1);
1592 }, 250);
1593 }
1594 if(Chat._gotServerError && Chat.intervalTimer){
@@ -1363,11 +1606,11 @@
1606 f.running = true;
1607 Chat._isBatchLoading = f.isFirstCall;
1608 if(true===f.isFirstCall){
1609 f.isFirstCall = false;
1610 Chat.ajaxStart();
1611 Chat.e.viewMessages.classList.add('loading');
1612 }
1613 F.fetch("chat-poll",{
1614 timeout: 420 * 1000/*FIXME: get the value from the server*/,
1615 urlParams:{
1616 name: Chat.mxMsg
@@ -1384,11 +1627,14 @@
1627 resumed, and reportError() produces a loud error message. */
1628 afterFetch();
1629 },
1630 onload:function(y){
1631 newcontent(y);
1632 if(Chat._isBatchLoading){
1633 Chat._isBatchLoading = false;
1634 Chat.updateActiveUserList();
1635 }
1636 afterFetch();
1637 }
1638 });
1639 };
1640 poll.isFirstCall = true;
@@ -1395,7 +1641,15 @@
1641 Chat._gotServerError = poll.running = false;
1642 if( window.fossil.config.chat.fromcli ){
1643 Chat.chatOnlyMode(true);
1644 }
1645 Chat.intervalTimer = setInterval(poll, 1000);
1646 if(0){
1647 const flip = (ev)=>Chat.animate(ev.target,'anim-flip-h');
1648 document.querySelectorAll('#chat-edit-buttons button').forEach(function(e){
1649 e.addEventListener('click',flip, false);
1650 });
1651 }
1652 setTimeout( ()=>Chat.inputFocus(), 0 );
1653 Chat.animate.$disabled = false;
1654 F.page.chat = Chat/* enables testing the APIs via the dev tools */;
1655 })();
1656
--- src/clone.c
+++ src/clone.c
@@ -265,10 +265,14 @@
265265
fossil_fatal("server returned an error - clone aborted");
266266
}
267267
db_open_repository(zRepo);
268268
}
269269
db_begin_transaction();
270
+ if( db_exists("SELECT 1 FROM delta WHERE srcId IN phantom") ){
271
+ fossil_fatal("there are unresolved deltas -"
272
+ " the clone is probably incomplete and unusable.");
273
+ }
270274
fossil_print("Rebuilding repository meta-data...\n");
271275
rebuild_db(0, 1, 0);
272276
if( !noCompress ){
273277
fossil_print("Extra delta compression... "); fflush(stdout);
274278
extra_deltification();
275279
--- src/clone.c
+++ src/clone.c
@@ -265,10 +265,14 @@
265 fossil_fatal("server returned an error - clone aborted");
266 }
267 db_open_repository(zRepo);
268 }
269 db_begin_transaction();
 
 
 
 
270 fossil_print("Rebuilding repository meta-data...\n");
271 rebuild_db(0, 1, 0);
272 if( !noCompress ){
273 fossil_print("Extra delta compression... "); fflush(stdout);
274 extra_deltification();
275
--- src/clone.c
+++ src/clone.c
@@ -265,10 +265,14 @@
265 fossil_fatal("server returned an error - clone aborted");
266 }
267 db_open_repository(zRepo);
268 }
269 db_begin_transaction();
270 if( db_exists("SELECT 1 FROM delta WHERE srcId IN phantom") ){
271 fossil_fatal("there are unresolved deltas -"
272 " the clone is probably incomplete and unusable.");
273 }
274 fossil_print("Rebuilding repository meta-data...\n");
275 rebuild_db(0, 1, 0);
276 if( !noCompress ){
277 fossil_print("Extra delta compression... "); fflush(stdout);
278 extra_deltification();
279
+17 -8
--- src/default.css
+++ src/default.css
@@ -602,11 +602,16 @@
602602
tr.diffskip > td.chunkctrl {
603603
text-align: left;
604604
font-family: monospace;
605605
}
606606
tr.diffskip > td.chunkctrl > div {
607
- /* Exists solely for layout purposes. */
607
+ display: flex;
608
+ align-items: center;
609
+}
610
+tr.diffskip > td.chunkctrl > div > span.error {
611
+ padding: 0.25em 0.5em;
612
+ border-radius: 0.5em;
608613
}
609614
tr.diffskip > td.chunkctrl .jcbutton
610615
/* class name .button breaks w/ some skins! */ {
611616
min-width: 3.5ex;
612617
max-width: 3.5ex;
@@ -1207,10 +1212,13 @@
12071212
font-size: 1.2em;
12081213
padding: 0.2em;
12091214
margin: 0.25em 0;
12101215
flex: 0 0 auto;
12111216
}
1217
+.font-size-80 {
1218
+ font-size: 80%;
1219
+}
12121220
.font-size-100 {
12131221
font-size: 100%;
12141222
}
12151223
.font-size-125 {
12161224
font-size: 125%;
@@ -1301,16 +1309,17 @@
13011309
padding: 0.25em 0 0 0 /*prevents slight overlap at top */;
13021310
}
13031311
table.numbered-lines td.line-numbers {
13041312
width: 4.5em;
13051313
}
1306
-table.numbered-lines td.line-numbers > span:first-of-type {
1307
- margin-top: 0.25em/*must match top PADDING of
1308
- td.file-content > pre > code*/;
1314
+table.numbered-lines td.line-numbers > pre {
1315
+ margin: 0.25em/*must match top PADDING of td.file-content
1316
+ > pre > code*/ 0 0 0;
1317
+ padding: 0;
13091318
}
1310
-table.numbered-lines td.line-numbers > span {
1311
- display: block;
1319
+table.numbered-lines td.line-numbers span {
1320
+ display: inline-block;
13121321
margin: 0;
13131322
padding: 0;
13141323
line-height: inherit;
13151324
font-size: inherit;
13161325
font-family: inherit;
@@ -1317,11 +1326,11 @@
13171326
cursor: pointer;
13181327
white-space: pre;
13191328
margin-right: 2px/*keep selection from nudging the right column */;
13201329
text-align: right;
13211330
}
1322
-table.numbered-lines td.line-numbers > span:hover {
1331
+table.numbered-lines td.line-numbers span:hover {
13231332
background-color: rgba(112, 112, 112, 0.25);
13241333
}
13251334
table.numbered-lines td.file-content {
13261335
padding-left: 0.25em;
13271336
}
@@ -1378,11 +1387,11 @@
13781387
13791388
.fossil-tooltip {
13801389
text-align: center;
13811390
padding: 0.2em 1em;
13821391
border: 1px solid black;
1383
- border-radius: 0.25em;
1392
+ border-radius: 0.5em;
13841393
position: absolute;
13851394
display: inline-block;
13861395
z-index: 19/*below default skin's hamburger popup*/;
13871396
box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
13881397
background-color: inherit;
13891398
--- src/default.css
+++ src/default.css
@@ -602,11 +602,16 @@
602 tr.diffskip > td.chunkctrl {
603 text-align: left;
604 font-family: monospace;
605 }
606 tr.diffskip > td.chunkctrl > div {
607 /* Exists solely for layout purposes. */
 
 
 
 
 
608 }
609 tr.diffskip > td.chunkctrl .jcbutton
610 /* class name .button breaks w/ some skins! */ {
611 min-width: 3.5ex;
612 max-width: 3.5ex;
@@ -1207,10 +1212,13 @@
1207 font-size: 1.2em;
1208 padding: 0.2em;
1209 margin: 0.25em 0;
1210 flex: 0 0 auto;
1211 }
 
 
 
1212 .font-size-100 {
1213 font-size: 100%;
1214 }
1215 .font-size-125 {
1216 font-size: 125%;
@@ -1301,16 +1309,17 @@
1301 padding: 0.25em 0 0 0 /*prevents slight overlap at top */;
1302 }
1303 table.numbered-lines td.line-numbers {
1304 width: 4.5em;
1305 }
1306 table.numbered-lines td.line-numbers > span:first-of-type {
1307 margin-top: 0.25em/*must match top PADDING of
1308 td.file-content > pre > code*/;
 
1309 }
1310 table.numbered-lines td.line-numbers > span {
1311 display: block;
1312 margin: 0;
1313 padding: 0;
1314 line-height: inherit;
1315 font-size: inherit;
1316 font-family: inherit;
@@ -1317,11 +1326,11 @@
1317 cursor: pointer;
1318 white-space: pre;
1319 margin-right: 2px/*keep selection from nudging the right column */;
1320 text-align: right;
1321 }
1322 table.numbered-lines td.line-numbers > span:hover {
1323 background-color: rgba(112, 112, 112, 0.25);
1324 }
1325 table.numbered-lines td.file-content {
1326 padding-left: 0.25em;
1327 }
@@ -1378,11 +1387,11 @@
1378
1379 .fossil-tooltip {
1380 text-align: center;
1381 padding: 0.2em 1em;
1382 border: 1px solid black;
1383 border-radius: 0.25em;
1384 position: absolute;
1385 display: inline-block;
1386 z-index: 19/*below default skin's hamburger popup*/;
1387 box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
1388 background-color: inherit;
1389
--- src/default.css
+++ src/default.css
@@ -602,11 +602,16 @@
602 tr.diffskip > td.chunkctrl {
603 text-align: left;
604 font-family: monospace;
605 }
606 tr.diffskip > td.chunkctrl > div {
607 display: flex;
608 align-items: center;
609 }
610 tr.diffskip > td.chunkctrl > div > span.error {
611 padding: 0.25em 0.5em;
612 border-radius: 0.5em;
613 }
614 tr.diffskip > td.chunkctrl .jcbutton
615 /* class name .button breaks w/ some skins! */ {
616 min-width: 3.5ex;
617 max-width: 3.5ex;
@@ -1207,10 +1212,13 @@
1212 font-size: 1.2em;
1213 padding: 0.2em;
1214 margin: 0.25em 0;
1215 flex: 0 0 auto;
1216 }
1217 .font-size-80 {
1218 font-size: 80%;
1219 }
1220 .font-size-100 {
1221 font-size: 100%;
1222 }
1223 .font-size-125 {
1224 font-size: 125%;
@@ -1301,16 +1309,17 @@
1309 padding: 0.25em 0 0 0 /*prevents slight overlap at top */;
1310 }
1311 table.numbered-lines td.line-numbers {
1312 width: 4.5em;
1313 }
1314 table.numbered-lines td.line-numbers > pre {
1315 margin: 0.25em/*must match top PADDING of td.file-content
1316 > pre > code*/ 0 0 0;
1317 padding: 0;
1318 }
1319 table.numbered-lines td.line-numbers span {
1320 display: inline-block;
1321 margin: 0;
1322 padding: 0;
1323 line-height: inherit;
1324 font-size: inherit;
1325 font-family: inherit;
@@ -1317,11 +1326,11 @@
1326 cursor: pointer;
1327 white-space: pre;
1328 margin-right: 2px/*keep selection from nudging the right column */;
1329 text-align: right;
1330 }
1331 table.numbered-lines td.line-numbers span:hover {
1332 background-color: rgba(112, 112, 112, 0.25);
1333 }
1334 table.numbered-lines td.file-content {
1335 padding-left: 0.25em;
1336 }
@@ -1378,11 +1387,11 @@
1387
1388 .fossil-tooltip {
1389 text-align: center;
1390 padding: 0.2em 1em;
1391 border: 1px solid black;
1392 border-radius: 0.5em;
1393 position: absolute;
1394 display: inline-block;
1395 z-index: 19/*below default skin's hamburger popup*/;
1396 box-shadow: -0.15em 0.15em 0.2em rgba(0, 0, 0, 0.75);
1397 background-color: inherit;
1398
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -33,11 +33,11 @@
3333
/* Default callack handlers for Diff.fetchArtifactChunk(),
3434
unless overridden by options passeed to that function. */
3535
beforesend: function(){},
3636
aftersend: function(){},
3737
onerror: function(e){
38
- F.toast.error("XHR error: ",e.message);
38
+ console.error("XHR error: ",e);
3939
}
4040
}
4141
}
4242
};
4343
/**
@@ -138,10 +138,11 @@
138138
this.e.td = D.addClass(
139139
/* Holder for our UI controls */
140140
D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4),
141141
'chunkctrl'
142142
);
143
+ this.e.msgWidget = D.addClass(D.span(), 'hidden');
143144
this.e.btnWrapper = D.div();
144145
D.append(this.e.td, this.e.btnWrapper);
145146
/**
146147
Depending on various factors, we need one or more of:
147148
@@ -188,10 +189,11 @@
188189
}
189190
//this.e.btnUp = btnUp;
190191
//this.e.btnDown = btnDown;
191192
if(btnDown) D.append(this.e.btnWrapper, btnDown);
192193
if(btnUp) D.append(this.e.btnWrapper, btnUp);
194
+ D.append(this.e.btnWrapper, this.e.msgWidget);
193195
/* For debugging only... */
194196
this.e.posState = D.span();
195197
D.append(this.e.btnWrapper, this.e.posState);
196198
this.updatePosDebug();
197199
};
@@ -295,10 +297,11 @@
295297
if(!lines.length){
296298
/* No more data to load */
297299
this.destroy();
298300
return this;
299301
}
302
+ this.msg(false);
300303
//console.debug("Loaded line range ",
301304
//urlParam.from,"-",urlParam.to, "fetchType ",fetchType);
302305
const lineno = [],
303306
trPrev = this.e.tr.previousElementSibling,
304307
trNext = this.e.tr.nextElementSibling,
@@ -311,14 +314,18 @@
311314
) ? trNext : false
312315
/* Truthy if we want to combine trPrev, the new content, and
313316
trNext into trPrev and then remove trNext. */;
314317
let i, td;
315318
if(!f.convertLines){
319
+ /* Reminder: string.replaceAll() is a relatively new
320
+ JS feature, not available in some still-widely-used
321
+ browser versions. */
322
+ f.rx = [[/&/g, '&amp;'], [/</g, '&lt;']];
316323
f.convertLines = function(li){
317
- return li.join('\n')
318
- .replaceAll('&','&amp;')
319
- .replaceAll('<','&lt;')+'\n';
324
+ var s = li.join('\n');
325
+ f.rx.forEach((a)=>s=s.replace(a[0],a[1]));
326
+ return s + '\n';
320327
};
321328
}
322329
if(1){ // LHS line numbers...
323330
const selector = '.difflnl > pre';
324331
td = tr.querySelector(selector);
@@ -340,11 +347,11 @@
340347
341348
if(1){// code block(s)...
342349
const selector = '.difftxt > pre';
343350
td = tr.querySelectorAll(selector);
344351
const code = f.convertLines(lines);
345
- let joinNdx = 0;
352
+ let joinNdx = 0/*selector[X] index to join together*/;
346353
td.forEach(function(e){
347354
const content = [e.innerHTML];
348355
if(doAppend) content.push(code);
349356
else content.unshift(code);
350357
if(joinTr){
@@ -454,10 +461,28 @@
454461
return this;
455462
}else{
456463
throw new Error("Unexpected 'fetchType' value.");
457464
}
458465
},
466
+
467
+ /**
468
+ Sets this widget's message to the given text. If the message
469
+ represents an error, the first argument must be truthy, else it
470
+ must be falsy. Returns this object.
471
+ */
472
+ msg: function(isError,txt){
473
+ if(txt){
474
+ if(isError) D.addClass(this.e.msgWidget, 'error');
475
+ else D.removeClass(this.e.msgWidget, 'error');
476
+ D.append(
477
+ D.removeClass(D.clearElement(this.e.msgWidget), 'hidden'),
478
+ txt);
479
+ }else{
480
+ D.addClass(D.clearElement(this.e.msgWidget), 'hidden');
481
+ }
482
+ return this;
483
+ },
459484
460485
/**
461486
Fetches and inserts a line chunk. fetchType is:
462487
463488
this.FetchType.NextUp = upwards from next chunk (this.pos.next)
@@ -480,18 +505,18 @@
480505
fetchChunk: function(fetchType){
481506
/* Forewarning, this is a bit confusing: when fetching the
482507
previous lines, we're doing so on behalf of the *next* diff
483508
chunk (this.pos.next), and vice versa. */
484509
if(this.$isFetching){
485
- F.toast.warning("Cannot load chunk while a load is pending.");
486
- return this;
510
+ return this.msg(true,"Cannot load chunk while a load is pending.");
487511
}
488512
if(fetchType===this.FetchType.NextUp && !this.pos.next
489513
|| fetchType===this.FetchType.PrevDown && !this.pos.prev){
490514
console.error("Attempt to fetch diff lines but don't have any.");
491515
return this;
492516
}
517
+ this.msg(false,"Fetching diff chunk...");
493518
const fOpt = {
494519
urlParams:{
495520
name: this.fileHash, from: 0, to: 0
496521
},
497522
aftersend: ()=>delete this.$isFetching,
@@ -528,10 +553,11 @@
528553
fetchType = this.FetchType.FillGap;
529554
}
530555
}
531556
this.$isFetching = true;
532557
//console.debug("fetchChunk(",fetchType,")",up);
558
+ fOpt.onerror = (err)=>this.msg(true,err.message);
533559
Diff.fetchArtifactChunk(fOpt);
534560
return this;
535561
}
536562
};
537563
538564
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -33,11 +33,11 @@
33 /* Default callack handlers for Diff.fetchArtifactChunk(),
34 unless overridden by options passeed to that function. */
35 beforesend: function(){},
36 aftersend: function(){},
37 onerror: function(e){
38 F.toast.error("XHR error: ",e.message);
39 }
40 }
41 }
42 };
43 /**
@@ -138,10 +138,11 @@
138 this.e.td = D.addClass(
139 /* Holder for our UI controls */
140 D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4),
141 'chunkctrl'
142 );
 
143 this.e.btnWrapper = D.div();
144 D.append(this.e.td, this.e.btnWrapper);
145 /**
146 Depending on various factors, we need one or more of:
147
@@ -188,10 +189,11 @@
188 }
189 //this.e.btnUp = btnUp;
190 //this.e.btnDown = btnDown;
191 if(btnDown) D.append(this.e.btnWrapper, btnDown);
192 if(btnUp) D.append(this.e.btnWrapper, btnUp);
 
193 /* For debugging only... */
194 this.e.posState = D.span();
195 D.append(this.e.btnWrapper, this.e.posState);
196 this.updatePosDebug();
197 };
@@ -295,10 +297,11 @@
295 if(!lines.length){
296 /* No more data to load */
297 this.destroy();
298 return this;
299 }
 
300 //console.debug("Loaded line range ",
301 //urlParam.from,"-",urlParam.to, "fetchType ",fetchType);
302 const lineno = [],
303 trPrev = this.e.tr.previousElementSibling,
304 trNext = this.e.tr.nextElementSibling,
@@ -311,14 +314,18 @@
311 ) ? trNext : false
312 /* Truthy if we want to combine trPrev, the new content, and
313 trNext into trPrev and then remove trNext. */;
314 let i, td;
315 if(!f.convertLines){
 
 
 
 
316 f.convertLines = function(li){
317 return li.join('\n')
318 .replaceAll('&','&amp;')
319 .replaceAll('<','&lt;')+'\n';
320 };
321 }
322 if(1){ // LHS line numbers...
323 const selector = '.difflnl > pre';
324 td = tr.querySelector(selector);
@@ -340,11 +347,11 @@
340
341 if(1){// code block(s)...
342 const selector = '.difftxt > pre';
343 td = tr.querySelectorAll(selector);
344 const code = f.convertLines(lines);
345 let joinNdx = 0;
346 td.forEach(function(e){
347 const content = [e.innerHTML];
348 if(doAppend) content.push(code);
349 else content.unshift(code);
350 if(joinTr){
@@ -454,10 +461,28 @@
454 return this;
455 }else{
456 throw new Error("Unexpected 'fetchType' value.");
457 }
458 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
460 /**
461 Fetches and inserts a line chunk. fetchType is:
462
463 this.FetchType.NextUp = upwards from next chunk (this.pos.next)
@@ -480,18 +505,18 @@
480 fetchChunk: function(fetchType){
481 /* Forewarning, this is a bit confusing: when fetching the
482 previous lines, we're doing so on behalf of the *next* diff
483 chunk (this.pos.next), and vice versa. */
484 if(this.$isFetching){
485 F.toast.warning("Cannot load chunk while a load is pending.");
486 return this;
487 }
488 if(fetchType===this.FetchType.NextUp && !this.pos.next
489 || fetchType===this.FetchType.PrevDown && !this.pos.prev){
490 console.error("Attempt to fetch diff lines but don't have any.");
491 return this;
492 }
 
493 const fOpt = {
494 urlParams:{
495 name: this.fileHash, from: 0, to: 0
496 },
497 aftersend: ()=>delete this.$isFetching,
@@ -528,10 +553,11 @@
528 fetchType = this.FetchType.FillGap;
529 }
530 }
531 this.$isFetching = true;
532 //console.debug("fetchChunk(",fetchType,")",up);
 
533 Diff.fetchArtifactChunk(fOpt);
534 return this;
535 }
536 };
537
538
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -33,11 +33,11 @@
33 /* Default callack handlers for Diff.fetchArtifactChunk(),
34 unless overridden by options passeed to that function. */
35 beforesend: function(){},
36 aftersend: function(){},
37 onerror: function(e){
38 console.error("XHR error: ",e);
39 }
40 }
41 }
42 };
43 /**
@@ -138,10 +138,11 @@
138 this.e.td = D.addClass(
139 /* Holder for our UI controls */
140 D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4),
141 'chunkctrl'
142 );
143 this.e.msgWidget = D.addClass(D.span(), 'hidden');
144 this.e.btnWrapper = D.div();
145 D.append(this.e.td, this.e.btnWrapper);
146 /**
147 Depending on various factors, we need one or more of:
148
@@ -188,10 +189,11 @@
189 }
190 //this.e.btnUp = btnUp;
191 //this.e.btnDown = btnDown;
192 if(btnDown) D.append(this.e.btnWrapper, btnDown);
193 if(btnUp) D.append(this.e.btnWrapper, btnUp);
194 D.append(this.e.btnWrapper, this.e.msgWidget);
195 /* For debugging only... */
196 this.e.posState = D.span();
197 D.append(this.e.btnWrapper, this.e.posState);
198 this.updatePosDebug();
199 };
@@ -295,10 +297,11 @@
297 if(!lines.length){
298 /* No more data to load */
299 this.destroy();
300 return this;
301 }
302 this.msg(false);
303 //console.debug("Loaded line range ",
304 //urlParam.from,"-",urlParam.to, "fetchType ",fetchType);
305 const lineno = [],
306 trPrev = this.e.tr.previousElementSibling,
307 trNext = this.e.tr.nextElementSibling,
@@ -311,14 +314,18 @@
314 ) ? trNext : false
315 /* Truthy if we want to combine trPrev, the new content, and
316 trNext into trPrev and then remove trNext. */;
317 let i, td;
318 if(!f.convertLines){
319 /* Reminder: string.replaceAll() is a relatively new
320 JS feature, not available in some still-widely-used
321 browser versions. */
322 f.rx = [[/&/g, '&amp;'], [/</g, '&lt;']];
323 f.convertLines = function(li){
324 var s = li.join('\n');
325 f.rx.forEach((a)=>s=s.replace(a[0],a[1]));
326 return s + '\n';
327 };
328 }
329 if(1){ // LHS line numbers...
330 const selector = '.difflnl > pre';
331 td = tr.querySelector(selector);
@@ -340,11 +347,11 @@
347
348 if(1){// code block(s)...
349 const selector = '.difftxt > pre';
350 td = tr.querySelectorAll(selector);
351 const code = f.convertLines(lines);
352 let joinNdx = 0/*selector[X] index to join together*/;
353 td.forEach(function(e){
354 const content = [e.innerHTML];
355 if(doAppend) content.push(code);
356 else content.unshift(code);
357 if(joinTr){
@@ -454,10 +461,28 @@
461 return this;
462 }else{
463 throw new Error("Unexpected 'fetchType' value.");
464 }
465 },
466
467 /**
468 Sets this widget's message to the given text. If the message
469 represents an error, the first argument must be truthy, else it
470 must be falsy. Returns this object.
471 */
472 msg: function(isError,txt){
473 if(txt){
474 if(isError) D.addClass(this.e.msgWidget, 'error');
475 else D.removeClass(this.e.msgWidget, 'error');
476 D.append(
477 D.removeClass(D.clearElement(this.e.msgWidget), 'hidden'),
478 txt);
479 }else{
480 D.addClass(D.clearElement(this.e.msgWidget), 'hidden');
481 }
482 return this;
483 },
484
485 /**
486 Fetches and inserts a line chunk. fetchType is:
487
488 this.FetchType.NextUp = upwards from next chunk (this.pos.next)
@@ -480,18 +505,18 @@
505 fetchChunk: function(fetchType){
506 /* Forewarning, this is a bit confusing: when fetching the
507 previous lines, we're doing so on behalf of the *next* diff
508 chunk (this.pos.next), and vice versa. */
509 if(this.$isFetching){
510 return this.msg(true,"Cannot load chunk while a load is pending.");
 
511 }
512 if(fetchType===this.FetchType.NextUp && !this.pos.next
513 || fetchType===this.FetchType.PrevDown && !this.pos.prev){
514 console.error("Attempt to fetch diff lines but don't have any.");
515 return this;
516 }
517 this.msg(false,"Fetching diff chunk...");
518 const fOpt = {
519 urlParams:{
520 name: this.fileHash, from: 0, to: 0
521 },
522 aftersend: ()=>delete this.$isFetching,
@@ -528,10 +553,11 @@
553 fetchType = this.FetchType.FillGap;
554 }
555 }
556 this.$isFetching = true;
557 //console.debug("fetchChunk(",fetchType,")",up);
558 fOpt.onerror = (err)=>this.msg(true,err.message);
559 Diff.fetchArtifactChunk(fOpt);
560 return this;
561 }
562 };
563
564
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -119,13 +119,18 @@
119119
/** Returns a new TEXT node which contains the text of all of the
120120
arguments appended together. */
121121
dom.text = function(/*...*/){
122122
return document.createTextNode(argsToArray(arguments).join(''));
123123
};
124
- dom.button = function(label){
124
+ /** Returns a new Button element with the given optional
125
+ label and on-click event listener function. */
126
+ dom.button = function(label,callback){
125127
const b = this.create('button');
126128
if(label) b.appendChild(this.text(label));
129
+ if('function' === typeof callback){
130
+ b.addEventListener('click', callback, false);
131
+ }
127132
return b;
128133
};
129134
/**
130135
Returns a TEXTAREA element.
131136
@@ -677,10 +682,80 @@
677682
A DOM event handler which simply passes event.target
678683
to dom.flashOnce().
679684
*/
680685
dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target)
681686
687
+ /**
688
+ This variant of flashOnce() flashes the element e n times
689
+ for a duration of howLongMs milliseconds then calls the
690
+ afterFlashCallback() callback. It may also be called with 2
691
+ or 3 arguments, in which case:
692
+
693
+ 2 arguments: default flash time and no callback.
694
+
695
+ 3 arguments: 3rd may be a flash delay time or a callback
696
+ function.
697
+
698
+ Returns this object but the flashing is asynchronous.
699
+
700
+ Depending on system load and related factors, a multi-flash
701
+ animation might stutter and look suboptimal.
702
+ */
703
+ dom.flashNTimes = function(e,n,howLongMs,afterFlashCallback){
704
+ const args = argsToArray(arguments);
705
+ args.splice(1,1);
706
+ if(arguments.length===3 && 'function'===typeof howLongMs){
707
+ afterFlashCallback = howLongMs;
708
+ howLongMs = args[1] = this.flashOnce.defaultTimeMs;
709
+ }else if(arguments.length<3){
710
+ args[1] = this.flashOnce.defaultTimeMs;
711
+ }
712
+ n = +n;
713
+ const self = this;
714
+ const cb = args[2] = function f(){
715
+ if(--n){
716
+ setTimeout(()=>self.flashOnce(e, howLongMs, f),
717
+ howLongMs+(howLongMs*0.1)/*we need a slight gap here*/);
718
+ }else if(afterFlashCallback){
719
+ afterFlashCallback();
720
+ }
721
+ };
722
+ this.flashOnce.apply(this, args);
723
+ return this;
724
+ };
725
+
726
+ /**
727
+ Adds the given CSS class or array of CSS classes to the given
728
+ element or forEach-capable list of elements for howLongMs, then
729
+ removes it. If afterCallack is a function, it is called after the
730
+ CSS class is removed from all elements. If called with 3
731
+ arguments and the 3rd is a function, the 3rd is treated as a
732
+ callback and the default time (addClassBriefly.defaultTimeMs) is
733
+ used. If called with only 2 arguments, a time of
734
+ addClassBriefly.defaultTimeMs is used.
735
+
736
+ Passing a value of 0 for howLongMs causes the default value
737
+ to be applied.
738
+
739
+ Returns this object but the CSS removal is asynchronous.
740
+ */
741
+ dom.addClassBriefly = function f(e, className, howLongMs, afterCallback){
742
+ if(arguments.length<4 && 'function'===typeof howLongMs){
743
+ afterCallback = howLongMs;
744
+ howLongMs = f.defaultTimeMs;
745
+ }else if(arguments.length<3 || !+howLongMs){
746
+ howLongMs = f.defaultTimeMs;
747
+ }
748
+ this.addClass(e, className);
749
+ setTimeout(function(){
750
+ dom.removeClass(e, className);
751
+ if(afterCallback) afterCallback();
752
+ }, howLongMs);
753
+ return this;
754
+ };
755
+ dom.addClassBriefly.defaultTimeMs = 1000;
756
+
682757
/**
683758
Attempts to copy the given text to the system clipboard. Returns
684759
true if it succeeds, else false.
685760
*/
686761
dom.copyTextToClipboard = function(text){
687762
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -119,13 +119,18 @@
119 /** Returns a new TEXT node which contains the text of all of the
120 arguments appended together. */
121 dom.text = function(/*...*/){
122 return document.createTextNode(argsToArray(arguments).join(''));
123 };
124 dom.button = function(label){
 
 
125 const b = this.create('button');
126 if(label) b.appendChild(this.text(label));
 
 
 
127 return b;
128 };
129 /**
130 Returns a TEXTAREA element.
131
@@ -677,10 +682,80 @@
677 A DOM event handler which simply passes event.target
678 to dom.flashOnce().
679 */
680 dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target)
681
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682 /**
683 Attempts to copy the given text to the system clipboard. Returns
684 true if it succeeds, else false.
685 */
686 dom.copyTextToClipboard = function(text){
687
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -119,13 +119,18 @@
119 /** Returns a new TEXT node which contains the text of all of the
120 arguments appended together. */
121 dom.text = function(/*...*/){
122 return document.createTextNode(argsToArray(arguments).join(''));
123 };
124 /** Returns a new Button element with the given optional
125 label and on-click event listener function. */
126 dom.button = function(label,callback){
127 const b = this.create('button');
128 if(label) b.appendChild(this.text(label));
129 if('function' === typeof callback){
130 b.addEventListener('click', callback, false);
131 }
132 return b;
133 };
134 /**
135 Returns a TEXTAREA element.
136
@@ -677,10 +682,80 @@
682 A DOM event handler which simply passes event.target
683 to dom.flashOnce().
684 */
685 dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target)
686
687 /**
688 This variant of flashOnce() flashes the element e n times
689 for a duration of howLongMs milliseconds then calls the
690 afterFlashCallback() callback. It may also be called with 2
691 or 3 arguments, in which case:
692
693 2 arguments: default flash time and no callback.
694
695 3 arguments: 3rd may be a flash delay time or a callback
696 function.
697
698 Returns this object but the flashing is asynchronous.
699
700 Depending on system load and related factors, a multi-flash
701 animation might stutter and look suboptimal.
702 */
703 dom.flashNTimes = function(e,n,howLongMs,afterFlashCallback){
704 const args = argsToArray(arguments);
705 args.splice(1,1);
706 if(arguments.length===3 && 'function'===typeof howLongMs){
707 afterFlashCallback = howLongMs;
708 howLongMs = args[1] = this.flashOnce.defaultTimeMs;
709 }else if(arguments.length<3){
710 args[1] = this.flashOnce.defaultTimeMs;
711 }
712 n = +n;
713 const self = this;
714 const cb = args[2] = function f(){
715 if(--n){
716 setTimeout(()=>self.flashOnce(e, howLongMs, f),
717 howLongMs+(howLongMs*0.1)/*we need a slight gap here*/);
718 }else if(afterFlashCallback){
719 afterFlashCallback();
720 }
721 };
722 this.flashOnce.apply(this, args);
723 return this;
724 };
725
726 /**
727 Adds the given CSS class or array of CSS classes to the given
728 element or forEach-capable list of elements for howLongMs, then
729 removes it. If afterCallack is a function, it is called after the
730 CSS class is removed from all elements. If called with 3
731 arguments and the 3rd is a function, the 3rd is treated as a
732 callback and the default time (addClassBriefly.defaultTimeMs) is
733 used. If called with only 2 arguments, a time of
734 addClassBriefly.defaultTimeMs is used.
735
736 Passing a value of 0 for howLongMs causes the default value
737 to be applied.
738
739 Returns this object but the CSS removal is asynchronous.
740 */
741 dom.addClassBriefly = function f(e, className, howLongMs, afterCallback){
742 if(arguments.length<4 && 'function'===typeof howLongMs){
743 afterCallback = howLongMs;
744 howLongMs = f.defaultTimeMs;
745 }else if(arguments.length<3 || !+howLongMs){
746 howLongMs = f.defaultTimeMs;
747 }
748 this.addClass(e, className);
749 setTimeout(function(){
750 dom.removeClass(e, className);
751 if(afterCallback) afterCallback();
752 }, howLongMs);
753 return this;
754 };
755 dom.addClassBriefly.defaultTimeMs = 1000;
756
757 /**
758 Attempts to copy the given text to the system clipboard. Returns
759 true if it succeeds, else false.
760 */
761 dom.copyTextToClipboard = function(text){
762
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -355,10 +355,11 @@
355355
*/
356356
setup: function f(){
357357
if(!f.hasOwnProperty('clickHandler')){
358358
f.clickHandler = function fch(ev){
359359
ev.preventDefault();
360
+ ev.stopPropagation();
360361
if(!fch.popup){
361362
fch.popup = new F.PopupWidget({
362363
cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
363364
refresh: function(){
364365
}
@@ -411,10 +412,11 @@
411412
x -= popupRect.width/2;
412413
}
413414
if(x<0) x = 0;
414415
//console.debug("dimensions",x,y, popupRect, rectBody);
415416
fch.popup.show(x, y);
417
+ return false;
416418
};
417419
f.foreachElement = function(e){
418420
if(e.classList.contains('processed')) return;
419421
e.classList.add('processed');
420422
e.$helpContent = [];
421423
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -355,10 +355,11 @@
355 */
356 setup: function f(){
357 if(!f.hasOwnProperty('clickHandler')){
358 f.clickHandler = function fch(ev){
359 ev.preventDefault();
 
360 if(!fch.popup){
361 fch.popup = new F.PopupWidget({
362 cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
363 refresh: function(){
364 }
@@ -411,10 +412,11 @@
411 x -= popupRect.width/2;
412 }
413 if(x<0) x = 0;
414 //console.debug("dimensions",x,y, popupRect, rectBody);
415 fch.popup.show(x, y);
 
416 };
417 f.foreachElement = function(e){
418 if(e.classList.contains('processed')) return;
419 e.classList.add('processed');
420 e.$helpContent = [];
421
--- src/fossil.popupwidget.js
+++ src/fossil.popupwidget.js
@@ -355,10 +355,11 @@
355 */
356 setup: function f(){
357 if(!f.hasOwnProperty('clickHandler')){
358 f.clickHandler = function fch(ev){
359 ev.preventDefault();
360 ev.stopPropagation();
361 if(!fch.popup){
362 fch.popup = new F.PopupWidget({
363 cssClass: ['fossil-tooltip', 'help-buttonlet-content'],
364 refresh: function(){
365 }
@@ -411,10 +412,11 @@
412 x -= popupRect.width/2;
413 }
414 if(x<0) x = 0;
415 //console.debug("dimensions",x,y, popupRect, rectBody);
416 fch.popup.show(x, y);
417 return false;
418 };
419 f.foreachElement = function(e){
420 if(e.classList.contains('processed')) return;
421 e.classList.add('processed');
422 e.$helpContent = [];
423
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -1,8 +1,8 @@
11
(function(F){
22
/**
3
- fossil.store is a basic wrapper around localStorage
3
+ fossil.storage is a basic wrapper around localStorage
44
or sessionStorage or a dummy proxy object if neither
55
of those are available.
66
*/
77
const tryStorage = function f(obj){
88
if(!f.key) f.key = 'fossil.access.check';
99
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -1,8 +1,8 @@
1 (function(F){
2 /**
3 fossil.store is a basic wrapper around localStorage
4 or sessionStorage or a dummy proxy object if neither
5 of those are available.
6 */
7 const tryStorage = function f(obj){
8 if(!f.key) f.key = 'fossil.access.check';
9
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -1,8 +1,8 @@
1 (function(F){
2 /**
3 fossil.storage is a basic wrapper around localStorage
4 or sessionStorage or a dummy proxy object if neither
5 of those are available.
6 */
7 const tryStorage = function f(obj){
8 if(!f.key) f.key = 'fossil.access.check';
9
+9 -3
--- src/info.c
+++ src/info.c
@@ -2248,11 +2248,11 @@
22482248
iStart = iEnd = atoi(&zLn[i++]);
22492249
}while( zLn[i] && iStart && iEnd );
22502250
}
22512251
/*cgi_printf("<!-- ln span count=%d -->", nSpans);*/
22522252
cgi_append_content("<table class='numbered-lines'><tbody>"
2253
- "<tr><td class='line-numbers'>", -1);
2253
+ "<tr><td class='line-numbers'><pre>", -1);
22542254
iStart = iEnd = 0;
22552255
count_lines(z, nZ, &nLine);
22562256
for( n=1 ; n<=nLine; ++n ){
22572257
const char * zAttr = "";
22582258
const char * zId = "";
@@ -2290,13 +2290,14 @@
22902290
zAttr = " class='selected-line end'";
22912291
iEnd = 0;
22922292
}else if( n>iStart && n<iEnd ){
22932293
zAttr = " class='selected-line'";
22942294
}
2295
- cgi_printf("<span%s%s>%6d</span>", zId, zAttr, n);
2295
+ cgi_printf("<span%s%s>%6d</span>\n", zId, zAttr, n)
2296
+ /* ^^^ explicit \n is necessary for text-mode browsers. */;
22962297
}
2297
- cgi_append_content("</td><td class='file-content'><pre>",-1);
2298
+ cgi_append_content("</pre></td><td class='file-content'><pre>",-1);
22982299
if(zExt && *zExt){
22992300
cgi_printf("<code class='language-%h'>",zExt);
23002301
}else{
23012302
cgi_append_content("<code>", -1);
23022303
}
@@ -3304,10 +3305,15 @@
33043305
@ <input type="checkbox" name="pclr" checked="checked" />
33053306
}else{
33063307
@ <input type="checkbox" name="pclr" />
33073308
}
33083309
@ Propagate color to descendants</label></div>
3310
+ @ <div class='font-size-80'>Be aware that fixed background
3311
+ @ colors will not interact well with all available skins.
3312
+ @ It is recommended that fossil be allowed to select these
3313
+ @ colors automatically so that it can take the skin's
3314
+ @ preferences into account.</div>
33093315
@ </td></tr>
33103316
33113317
@ <tr><th align="right" valign="top">Tags:</th>
33123318
@ <td valign="top">
33133319
@ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag) />
33143320
--- src/info.c
+++ src/info.c
@@ -2248,11 +2248,11 @@
2248 iStart = iEnd = atoi(&zLn[i++]);
2249 }while( zLn[i] && iStart && iEnd );
2250 }
2251 /*cgi_printf("<!-- ln span count=%d -->", nSpans);*/
2252 cgi_append_content("<table class='numbered-lines'><tbody>"
2253 "<tr><td class='line-numbers'>", -1);
2254 iStart = iEnd = 0;
2255 count_lines(z, nZ, &nLine);
2256 for( n=1 ; n<=nLine; ++n ){
2257 const char * zAttr = "";
2258 const char * zId = "";
@@ -2290,13 +2290,14 @@
2290 zAttr = " class='selected-line end'";
2291 iEnd = 0;
2292 }else if( n>iStart && n<iEnd ){
2293 zAttr = " class='selected-line'";
2294 }
2295 cgi_printf("<span%s%s>%6d</span>", zId, zAttr, n);
 
2296 }
2297 cgi_append_content("</td><td class='file-content'><pre>",-1);
2298 if(zExt && *zExt){
2299 cgi_printf("<code class='language-%h'>",zExt);
2300 }else{
2301 cgi_append_content("<code>", -1);
2302 }
@@ -3304,10 +3305,15 @@
3304 @ <input type="checkbox" name="pclr" checked="checked" />
3305 }else{
3306 @ <input type="checkbox" name="pclr" />
3307 }
3308 @ Propagate color to descendants</label></div>
 
 
 
 
 
3309 @ </td></tr>
3310
3311 @ <tr><th align="right" valign="top">Tags:</th>
3312 @ <td valign="top">
3313 @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag) />
3314
--- src/info.c
+++ src/info.c
@@ -2248,11 +2248,11 @@
2248 iStart = iEnd = atoi(&zLn[i++]);
2249 }while( zLn[i] && iStart && iEnd );
2250 }
2251 /*cgi_printf("<!-- ln span count=%d -->", nSpans);*/
2252 cgi_append_content("<table class='numbered-lines'><tbody>"
2253 "<tr><td class='line-numbers'><pre>", -1);
2254 iStart = iEnd = 0;
2255 count_lines(z, nZ, &nLine);
2256 for( n=1 ; n<=nLine; ++n ){
2257 const char * zAttr = "";
2258 const char * zId = "";
@@ -2290,13 +2290,14 @@
2290 zAttr = " class='selected-line end'";
2291 iEnd = 0;
2292 }else if( n>iStart && n<iEnd ){
2293 zAttr = " class='selected-line'";
2294 }
2295 cgi_printf("<span%s%s>%6d</span>\n", zId, zAttr, n)
2296 /* ^^^ explicit \n is necessary for text-mode browsers. */;
2297 }
2298 cgi_append_content("</pre></td><td class='file-content'><pre>",-1);
2299 if(zExt && *zExt){
2300 cgi_printf("<code class='language-%h'>",zExt);
2301 }else{
2302 cgi_append_content("<code>", -1);
2303 }
@@ -3304,10 +3305,15 @@
3305 @ <input type="checkbox" name="pclr" checked="checked" />
3306 }else{
3307 @ <input type="checkbox" name="pclr" />
3308 }
3309 @ Propagate color to descendants</label></div>
3310 @ <div class='font-size-80'>Be aware that fixed background
3311 @ colors will not interact well with all available skins.
3312 @ It is recommended that fossil be allowed to select these
3313 @ colors automatically so that it can take the skin's
3314 @ preferences into account.</div>
3315 @ </td></tr>
3316
3317 @ <tr><th align="right" valign="top">Tags:</th>
3318 @ <td valign="top">
3319 @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag) />
3320
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933933
if(offset>0 && !fossil_isspace(data[-1])){
934934
/* Only ever match if the *previous* character is
935935
whitespace or we're at the start of the input. */
936936
return 0;
937937
}
938
+ assert( '#' == data[0] );
938939
if(size < 2 || !fossil_isalnum(data[1])) return 0;
939940
/*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
940941
for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
941942
/*TODO: in order to support detection of forum post-style
942943
references, we need to recognize #X.Y, but only when X and Y are
943944
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933 if(offset>0 && !fossil_isspace(data[-1])){
934 /* Only ever match if the *previous* character is
935 whitespace or we're at the start of the input. */
936 return 0;
937 }
 
938 if(size < 2 || !fossil_isalnum(data[1])) return 0;
939 /*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
940 for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
941 /*TODO: in order to support detection of forum post-style
942 references, we need to recognize #X.Y, but only when X and Y are
943
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933 if(offset>0 && !fossil_isspace(data[-1])){
934 /* Only ever match if the *previous* character is
935 whitespace or we're at the start of the input. */
936 return 0;
937 }
938 assert( '#' == data[0] );
939 if(size < 2 || !fossil_isalnum(data[1])) return 0;
940 /*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
941 for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
942 /*TODO: in order to support detection of forum post-style
943 references, we need to recognize #X.Y, but only when X and Y are
944
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933933
if(offset>0 && !fossil_isspace(data[-1])){
934934
/* Only ever match if the *previous* character is
935935
whitespace or we're at the start of the input. */
936936
return 0;
937937
}
938
+ assert( '#' == data[0] );
938939
if(size < 2 || !fossil_isalnum(data[1])) return 0;
939940
/*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
940941
for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
941942
/*TODO: in order to support detection of forum post-style
942943
references, we need to recognize #X.Y, but only when X and Y are
943944
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933 if(offset>0 && !fossil_isspace(data[-1])){
934 /* Only ever match if the *previous* character is
935 whitespace or we're at the start of the input. */
936 return 0;
937 }
 
938 if(size < 2 || !fossil_isalnum(data[1])) return 0;
939 /*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
940 for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
941 /*TODO: in order to support detection of forum post-style
942 references, we need to recognize #X.Y, but only when X and Y are
943
--- src/markdown.c
+++ src/markdown.c
@@ -933,10 +933,11 @@
933 if(offset>0 && !fossil_isspace(data[-1])){
934 /* Only ever match if the *previous* character is
935 whitespace or we're at the start of the input. */
936 return 0;
937 }
938 assert( '#' == data[0] );
939 if(size < 2 || !fossil_isalnum(data[1])) return 0;
940 /*fprintf(stderr,"HASHREF: %.*s\n", (int)size, data);*/
941 for (end = 2; (end < size) && fossil_isalnum(data[end]); ++end);
942 /*TODO: in order to support detection of forum post-style
943 references, we need to recognize #X.Y, but only when X and Y are
944
+233 -77
--- src/shell.c
+++ src/shell.c
@@ -651,23 +651,42 @@
651651
}
652652
return n;
653653
}
654654
655655
/*
656
-** Return true if zFile does not exist or if it is not an ordinary file.
656
+** Return open FILE * if zFile exists, can be opened for read
657
+** and is an ordinary file or a character stream source.
658
+** Otherwise return 0.
657659
*/
660
+static FILE * openChrSource(const char *zFile){
658661
#ifdef _WIN32
659
-# define notNormalFile(X) 0
662
+ struct _stat x = {0};
663
+# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0)
664
+ /* On Windows, open first, then check the stream nature. This order
665
+ ** is necessary because _stat() and sibs, when checking a named pipe,
666
+ ** effectively break the pipe as its supplier sees it. */
667
+ FILE *rv = fopen(zFile, "rb");
668
+ if( rv==0 ) return 0;
669
+ if( _fstat(_fileno(rv), &x) != 0
670
+ || !STAT_CHR_SRC(x.st_mode)){
671
+ fclose(rv);
672
+ rv = 0;
673
+ }
674
+ return rv;
660675
#else
661
-static int notNormalFile(const char *zFile){
662
- struct stat x;
663
- int rc;
664
- memset(&x, 0, sizeof(x));
665
- rc = stat(zFile, &x);
666
- return rc || !S_ISREG(x.st_mode);
667
-}
668
-#endif
676
+ struct stat x = {0};
677
+ int rc = stat(zFile, &x);
678
+# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode))
679
+ if( rc!=0 ) return 0;
680
+ if( STAT_CHR_SRC(x.st_mode) ){
681
+ return fopen(zFile, "rb");
682
+ }else{
683
+ return 0;
684
+ }
685
+#endif
686
+#undef STAT_CHR_SRC
687
+}
669688
670689
/*
671690
** This routine reads a line of text from FILE in, stores
672691
** the text in memory obtained from malloc() and returns a pointer
673692
** to the text. NULL is returned at end of file, or if malloc()
@@ -2202,10 +2221,15 @@
22022221
**
22032222
** If a non-NULL value is specified for the optional $dir parameter and
22042223
** $path is a relative path, then $path is interpreted relative to $dir.
22052224
** And the paths returned in the "name" column of the table are also
22062225
** relative to directory $dir.
2226
+**
2227
+** Notes on building this extension for Windows:
2228
+** Unless linked statically with the SQLite library, a preprocessor
2229
+** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone
2230
+** DLL form of this extension for WIN32. See its use below for details.
22072231
*/
22082232
/* #include "sqlite3ext.h" */
22092233
SQLITE_EXTENSION_INIT1
22102234
#include <stdio.h>
22112235
#include <string.h>
@@ -2355,10 +2379,26 @@
23552379
fileIntervals.HighPart = pFileTime->dwHighDateTime;
23562380
23572381
return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
23582382
}
23592383
2384
+
2385
+#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
2386
+# /* To allow a standalone DLL, use this next replacement function: */
2387
+# undef sqlite3_win32_utf8_to_unicode
2388
+# define sqlite3_win32_utf8_to_unicode utf8_to_utf16
2389
+#
2390
+LPWSTR utf8_to_utf16(const char *z){
2391
+ int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0);
2392
+ LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR));
2393
+ if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) )
2394
+ return rv;
2395
+ sqlite3_free(rv);
2396
+ return 0;
2397
+}
2398
+#endif
2399
+
23602400
/*
23612401
** This function attempts to normalize the time values found in the stat()
23622402
** buffer to UTC. This is necessary on Win32, where the runtime library
23632403
** appears to return these values as local times.
23642404
*/
@@ -3128,10 +3168,18 @@
31283168
if( rc==SQLITE_OK ){
31293169
rc = fsdirRegister(db);
31303170
}
31313171
return rc;
31323172
}
3173
+
3174
+#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
3175
+/* To allow a standalone DLL, make test_windirent.c use the same
3176
+ * redefined SQLite API calls as the above extension code does.
3177
+ * Just pull in this .c to accomplish this. As a beneficial side
3178
+ * effect, this extension becomes a single translation unit. */
3179
+# include "test_windirent.c"
3180
+#endif
31333181
31343182
/************************* End ../ext/misc/fileio.c ********************/
31353183
/************************* Begin ../ext/misc/completion.c ******************/
31363184
/*
31373185
** 2017-07-10
@@ -10099,10 +10147,23 @@
1009910147
idxFinalize(&rc, pIdxList);
1010010148
1010110149
*pRc = rc;
1010210150
return 0;
1010310151
}
10152
+
10153
+/* Callback for sqlite3_exec() with query with leading count(*) column.
10154
+ * The first argument is expected to be an int*, referent to be incremented
10155
+ * if that leading column is not exactly '0'.
10156
+ */
10157
+static int countNonzeros(void* pCount, int nc,
10158
+ char* azResults[], char* azColumns[]){
10159
+ (void)azColumns; /* Suppress unused parameter warning */
10160
+ if( nc>0 && (azResults[0][0]!='0' || azResults[0][1]!=0) ){
10161
+ *((int *)pCount) += 1;
10162
+ }
10163
+ return 0;
10164
+}
1010410165
1010510166
static int idxCreateFromCons(
1010610167
sqlite3expert *p,
1010710168
IdxScan *pScan,
1010810169
IdxConstraint *pEq,
@@ -10126,30 +10187,57 @@
1012610187
}
1012710188
1012810189
if( rc==SQLITE_OK ){
1012910190
/* Hash the list of columns to come up with a name for the index */
1013010191
const char *zTable = pScan->pTab->zName;
10131
- char *zName; /* Index name */
10132
- int i;
10133
- for(i=0; zCols[i]; i++){
10134
- h += ((h<<3) + zCols[i]);
10135
- }
10136
- zName = sqlite3_mprintf("%s_idx_%08x", zTable, h);
10137
- if( zName==0 ){
10192
+ int quoteTable = idxIdentifierRequiresQuotes(zTable);
10193
+ char *zName = 0; /* Index name */
10194
+ int collisions = 0;
10195
+ do{
10196
+ int i;
10197
+ char *zFind;
10198
+ for(i=0; zCols[i]; i++){
10199
+ h += ((h<<3) + zCols[i]);
10200
+ }
10201
+ sqlite3_free(zName);
10202
+ zName = sqlite3_mprintf("%s_idx_%08x", zTable, h);
10203
+ if( zName==0 ) break;
10204
+ /* Is is unique among table, view and index names? */
10205
+ zFmt = "SELECT count(*) FROM sqlite_schema WHERE name=%Q"
10206
+ " AND type in ('index','table','view')";
10207
+ zFind = sqlite3_mprintf(zFmt, zName);
10208
+ i = 0;
10209
+ rc = sqlite3_exec(dbm, zFind, countNonzeros, &i, 0);
10210
+ assert(rc==SQLITE_OK);
10211
+ sqlite3_free(zFind);
10212
+ if( i==0 ){
10213
+ collisions = 0;
10214
+ break;
10215
+ }
10216
+ ++collisions;
10217
+ }while( collisions<50 && zName!=0 );
10218
+ if( collisions ){
10219
+ /* This return means "Gave up trying to find a unique index name." */
10220
+ rc = SQLITE_BUSY_TIMEOUT;
10221
+ }else if( zName==0 ){
1013810222
rc = SQLITE_NOMEM;
1013910223
}else{
10140
- if( idxIdentifierRequiresQuotes(zTable) ){
10141
- zFmt = "CREATE INDEX '%q' ON %Q(%s)";
10224
+ if( quoteTable ){
10225
+ zFmt = "CREATE INDEX \"%w\" ON \"%w\"(%s)";
1014210226
}else{
1014310227
zFmt = "CREATE INDEX %s ON %s(%s)";
1014410228
}
1014510229
zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols);
1014610230
if( !zIdx ){
1014710231
rc = SQLITE_NOMEM;
1014810232
}else{
1014910233
rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg);
10150
- idxHashAdd(&rc, &p->hIdx, zName, zIdx);
10234
+ if( rc!=SQLITE_OK ){
10235
+ rc = SQLITE_BUSY_TIMEOUT;
10236
+ }else{
10237
+ idxHashAdd(&rc, &p->hIdx, zName, zIdx);
10238
+ }
1015110239
}
1015210240
sqlite3_free(zName);
1015310241
sqlite3_free(zIdx);
1015410242
}
1015510243
}
@@ -11069,10 +11157,14 @@
1106911157
rc = idxProcessTriggers(p, pzErr);
1107011158
1107111159
/* Create candidate indexes within the in-memory database file */
1107211160
if( rc==SQLITE_OK ){
1107311161
rc = idxCreateCandidates(p);
11162
+ }else if ( rc==SQLITE_BUSY_TIMEOUT ){
11163
+ if( pzErr )
11164
+ *pzErr = sqlite3_mprintf("Cannot find a unique index name to propose.");
11165
+ return rc;
1107411166
}
1107511167
1107611168
/* Generate the stat1 data */
1107711169
if( rc==SQLITE_OK ){
1107811170
rc = idxPopulateStat1(p, pzErr);
@@ -12161,11 +12253,11 @@
1216112253
#define SHFLG_Backslash 0x00000004 /* The --backslash option is used */
1216212254
#define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
1216312255
#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
1216412256
#define SHFLG_CountChanges 0x00000020 /* .changes setting */
1216512257
#define SHFLG_Echo 0x00000040 /* .echo or --echo setting */
12166
-#define SHFLG_HeaderSet 0x00000080 /* .header has been used */
12258
+#define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */
1216712259
#define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */
1216812260
#define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */
1216912261
1217012262
/*
1217112263
** Macros for testing and setting shellFlgs
@@ -20197,11 +20289,11 @@
2019720289
}else{
2019820290
rc = process_input(p);
2019920291
pclose(p->in);
2020020292
}
2020120293
#endif
20202
- }else if( notNormalFile(azArg[1]) || (p->in = fopen(azArg[1], "rb"))==0 ){
20294
+ }else if( (p->in = openChrSource(azArg[1]))==0 ){
2020320295
utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
2020420296
rc = 1;
2020520297
}else{
2020620298
rc = process_input(p);
2020720299
fclose(p->in);
@@ -21556,59 +21648,114 @@
2155621648
}
2155721649
p->bSafeMode = p->bSafeModePersist;
2155821650
return rc;
2155921651
}
2156021652
21561
-/*
21562
-** Return TRUE if a semicolon occurs anywhere in the first N characters
21563
-** of string z[].
21564
-*/
21565
-static int line_contains_semicolon(const char *z, int N){
21566
- int i;
21567
- for(i=0; i<N; i++){ if( z[i]==';' ) return 1; }
21568
- return 0;
21569
-}
21570
-
21571
-/*
21572
-** Test to see if a line consists entirely of whitespace.
21573
-*/
21574
-static int _all_whitespace(const char *z){
21575
- for(; *z; z++){
21576
- if( IsSpace(z[0]) ) continue;
21577
- if( *z=='/' && z[1]=='*' ){
21578
- z += 2;
21579
- while( *z && (*z!='*' || z[1]!='/') ){ z++; }
21580
- if( *z==0 ) return 0;
21581
- z++;
21582
- continue;
21583
- }
21584
- if( *z=='-' && z[1]=='-' ){
21585
- z += 2;
21586
- while( *z && *z!='\n' ){ z++; }
21587
- if( *z==0 ) return 1;
21588
- continue;
21589
- }
21590
- return 0;
21591
- }
21592
- return 1;
21653
+/* Line scan result and intermediate states (supporting scan resumption)
21654
+*/
21655
+#ifndef CHAR_BIT
21656
+# define CHAR_BIT 8
21657
+#endif
21658
+typedef enum {
21659
+ QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT,
21660
+ QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT,
21661
+ QSS_Start = 0
21662
+} QuickScanState;
21663
+#define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask))
21664
+#define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start)
21665
+#define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start)
21666
+#define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark)
21667
+#define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi)
21668
+
21669
+/*
21670
+** Scan line for classification to guide shell's handling.
21671
+** The scan is resumable for subsequent lines when prior
21672
+** return values are passed as the 2nd argument.
21673
+*/
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 '-':
21684
+ if( *zLine!='-' )
21685
+ break;
21686
+ while((cin = *++zLine)!=0 )
21687
+ if( cin=='\n')
21688
+ goto PlainScan;
21689
+ return qss;
21690
+ case ';':
21691
+ qss |= QSS_EndingSemi;
21692
+ continue;
21693
+ case '/':
21694
+ if( *zLine=='*' ){
21695
+ ++zLine;
21696
+ cWait = '*';
21697
+ qss = QSS_SETV(qss, cWait);
21698
+ goto TermScan;
21699
+ }
21700
+ break;
21701
+ case '[':
21702
+ cin = ']';
21703
+ /* fall thru */
21704
+ case '`': case '\'': case '"':
21705
+ cWait = cin;
21706
+ qss = QSS_HasDark | cWait;
21707
+ goto TermScan;
21708
+ default:
21709
+ break;
21710
+ }
21711
+ qss = (qss & ~QSS_EndingSemi) | QSS_HasDark;
21712
+ }
21713
+ }else{
21714
+ TermScan:
21715
+ while( (cin = *zLine++)!=0 ){
21716
+ if( cin==cWait ){
21717
+ switch( cWait ){
21718
+ case '*':
21719
+ if( *zLine != '/' )
21720
+ continue;
21721
+ ++zLine;
21722
+ cWait = 0;
21723
+ qss = QSS_SETV(qss, 0);
21724
+ goto PlainScan;
21725
+ case '`': case '\'': case '"':
21726
+ if(*zLine==cWait){
21727
+ ++zLine;
21728
+ continue;
21729
+ }
21730
+ /* fall thru */
21731
+ case ']':
21732
+ cWait = 0;
21733
+ qss = QSS_SETV(qss, 0);
21734
+ goto PlainScan;
21735
+ default: assert(0);
21736
+ }
21737
+ }
21738
+ }
21739
+ }
21740
+ return qss;
2159321741
}
2159421742
2159521743
/*
2159621744
** Return TRUE if the line typed in is an SQL command terminator other
2159721745
** than a semi-colon. The SQL Server style "go" command is understood
2159821746
** as is the Oracle "/".
2159921747
*/
21600
-static int line_is_command_terminator(const char *zLine){
21748
+static int line_is_command_terminator(char *zLine){
2160121749
while( IsSpace(zLine[0]) ){ zLine++; };
21602
- if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){
21603
- return 1; /* Oracle */
21604
- }
21605
- if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o'
21606
- && _all_whitespace(&zLine[2]) ){
21607
- return 1; /* SQL Server */
21608
- }
21609
- return 0;
21750
+ if( zLine[0]=='/' )
21751
+ zLine += 1; /* Oracle */
21752
+ else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
21753
+ zLine += 2; /* SQL Server */
21754
+ else
21755
+ return 0;
21756
+ return quickscan(zLine,QSS_Start)==QSS_Start;
2161021757
}
2161121758
2161221759
/*
2161321760
** We need a default sqlite3_complete() implementation to use in case
2161421761
** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes
@@ -21661,12 +21808,15 @@
2166121808
}else{
2166221809
utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db));
2166321810
}
2166421811
return 1;
2166521812
}else if( ShellHasFlag(p, SHFLG_CountChanges) ){
21666
- raw_printf(p->out, "changes: %3lld total_changes: %lld\n",
21813
+ char zLineBuf[2000];
21814
+ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
21815
+ "changes: %lld total_changes: %lld",
2166721816
sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
21817
+ raw_printf(p->out, "%s\n", zLineBuf);
2166821818
}
2166921819
return 0;
2167021820
}
2167121821
2167221822
@@ -21683,14 +21833,14 @@
2168321833
char *zLine = 0; /* A single input line */
2168421834
char *zSql = 0; /* Accumulated SQL text */
2168521835
int nLine; /* Length of current line */
2168621836
int nSql = 0; /* Bytes of zSql[] used */
2168721837
int nAlloc = 0; /* Allocated zSql[] space */
21688
- int nSqlPrior = 0; /* Bytes of zSql[] used by prior line */
2168921838
int rc; /* Error code */
2169021839
int errCnt = 0; /* Number of errors seen */
2169121840
int startline = 0; /* Line number for start of current input */
21841
+ QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
2169221842
2169321843
p->lineno = 0;
2169421844
while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
2169521845
fflush(p->out);
2169621846
zLine = one_input_line(p->in, zLine, nSql>0);
@@ -21702,12 +21852,20 @@
2170221852
if( seenInterrupt ){
2170321853
if( p->in!=0 ) break;
2170421854
seenInterrupt = 0;
2170521855
}
2170621856
p->lineno++;
21707
- if( nSql==0 && _all_whitespace(zLine) ){
21708
- if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
21857
+ if( QSS_INPLAIN(qss)
21858
+ && line_is_command_terminator(zLine)
21859
+ && line_is_complete(zSql, nSql) ){
21860
+ memcpy(zLine,";",2);
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 */
2170921867
continue;
2171021868
}
2171121869
if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
2171221870
if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
2171321871
if( zLine[0]=='.' ){
@@ -21718,20 +21876,17 @@
2171821876
errCnt++;
2171921877
}
2172021878
}
2172121879
continue;
2172221880
}
21723
- if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){
21724
- memcpy(zLine,";",2);
21725
- }
2172621881
nLine = strlen30(zLine);
2172721882
if( nSql+nLine+2>=nAlloc ){
21728
- nAlloc = nSql+nLine+100;
21883
+ /* Grow buffer by half-again increments when big. */
21884
+ nAlloc = nSql+(nSql>>1)+nLine+100;
2172921885
zSql = realloc(zSql, nAlloc);
2173021886
if( zSql==0 ) shell_out_of_memory();
2173121887
}
21732
- nSqlPrior = nSql;
2173321888
if( nSql==0 ){
2173421889
int i;
2173521890
for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
2173621891
assert( nAlloc>0 && zSql!=0 );
2173721892
memcpy(zSql, zLine+i, nLine+1-i);
@@ -21740,27 +21895,26 @@
2174021895
}else{
2174121896
zSql[nSql++] = '\n';
2174221897
memcpy(zSql+nSql, zLine, nLine+1);
2174321898
nSql += nLine;
2174421899
}
21745
- if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior)
21746
- && sqlite3_complete(zSql) ){
21900
+ if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
2174721901
errCnt += runOneSqlLine(p, zSql, p->in, startline);
2174821902
nSql = 0;
2174921903
if( p->outCount ){
2175021904
output_reset(p);
2175121905
p->outCount = 0;
2175221906
}else{
2175321907
clearTempFile(p);
2175421908
}
2175521909
p->bSafeMode = p->bSafeModePersist;
21756
- }else if( nSql && _all_whitespace(zSql) ){
21910
+ }else if( nSql && QSS_PLAINWHITE(qss) ){
2175721911
if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
2175821912
nSql = 0;
2175921913
}
2176021914
}
21761
- if( nSql && !_all_whitespace(zSql) ){
21915
+ if( nSql && QSS_PLAINDARK(qss) ){
2176221916
errCnt += runOneSqlLine(p, zSql, p->in, startline);
2176321917
}
2176421918
free(zSql);
2176521919
free(zLine);
2176621920
return errCnt>0;
@@ -22399,12 +22553,14 @@
2239922553
}else if( strcmp(z,"-nullvalue")==0 ){
2240022554
sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
2240122555
"%s",cmdline_option_value(argc,argv,++i));
2240222556
}else if( strcmp(z,"-header")==0 ){
2240322557
data.showHeader = 1;
22404
- }else if( strcmp(z,"-noheader")==0 ){
22558
+ ShellSetFlag(&data, SHFLG_HeaderSet);
22559
+ }else if( strcmp(z,"-noheader")==0 ){
2240522560
data.showHeader = 0;
22561
+ ShellSetFlag(&data, SHFLG_HeaderSet);
2240622562
}else if( strcmp(z,"-echo")==0 ){
2240722563
ShellSetFlag(&data, SHFLG_Echo);
2240822564
}else if( strcmp(z,"-eqp")==0 ){
2240922565
data.autoEQP = AUTOEQP_on;
2241022566
}else if( strcmp(z,"-eqpfull")==0 ){
2241122567
--- src/shell.c
+++ src/shell.c
@@ -651,23 +651,42 @@
651 }
652 return n;
653 }
654
655 /*
656 ** Return true if zFile does not exist or if it is not an ordinary file.
 
 
657 */
 
658 #ifdef _WIN32
659 # define notNormalFile(X) 0
 
 
 
 
 
 
 
 
 
 
 
 
660 #else
661 static int notNormalFile(const char *zFile){
662 struct stat x;
663 int rc;
664 memset(&x, 0, sizeof(x));
665 rc = stat(zFile, &x);
666 return rc || !S_ISREG(x.st_mode);
667 }
668 #endif
 
 
 
 
669
670 /*
671 ** This routine reads a line of text from FILE in, stores
672 ** the text in memory obtained from malloc() and returns a pointer
673 ** to the text. NULL is returned at end of file, or if malloc()
@@ -2202,10 +2221,15 @@
2202 **
2203 ** If a non-NULL value is specified for the optional $dir parameter and
2204 ** $path is a relative path, then $path is interpreted relative to $dir.
2205 ** And the paths returned in the "name" column of the table are also
2206 ** relative to directory $dir.
 
 
 
 
 
2207 */
2208 /* #include "sqlite3ext.h" */
2209 SQLITE_EXTENSION_INIT1
2210 #include <stdio.h>
2211 #include <string.h>
@@ -2355,10 +2379,26 @@
2355 fileIntervals.HighPart = pFileTime->dwHighDateTime;
2356
2357 return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
2358 }
2359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2360 /*
2361 ** This function attempts to normalize the time values found in the stat()
2362 ** buffer to UTC. This is necessary on Win32, where the runtime library
2363 ** appears to return these values as local times.
2364 */
@@ -3128,10 +3168,18 @@
3128 if( rc==SQLITE_OK ){
3129 rc = fsdirRegister(db);
3130 }
3131 return rc;
3132 }
 
 
 
 
 
 
 
 
3133
3134 /************************* End ../ext/misc/fileio.c ********************/
3135 /************************* Begin ../ext/misc/completion.c ******************/
3136 /*
3137 ** 2017-07-10
@@ -10099,10 +10147,23 @@
10099 idxFinalize(&rc, pIdxList);
10100
10101 *pRc = rc;
10102 return 0;
10103 }
 
 
 
 
 
 
 
 
 
 
 
 
 
10104
10105 static int idxCreateFromCons(
10106 sqlite3expert *p,
10107 IdxScan *pScan,
10108 IdxConstraint *pEq,
@@ -10126,30 +10187,57 @@
10126 }
10127
10128 if( rc==SQLITE_OK ){
10129 /* Hash the list of columns to come up with a name for the index */
10130 const char *zTable = pScan->pTab->zName;
10131 char *zName; /* Index name */
10132 int i;
10133 for(i=0; zCols[i]; i++){
10134 h += ((h<<3) + zCols[i]);
10135 }
10136 zName = sqlite3_mprintf("%s_idx_%08x", zTable, h);
10137 if( zName==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10138 rc = SQLITE_NOMEM;
10139 }else{
10140 if( idxIdentifierRequiresQuotes(zTable) ){
10141 zFmt = "CREATE INDEX '%q' ON %Q(%s)";
10142 }else{
10143 zFmt = "CREATE INDEX %s ON %s(%s)";
10144 }
10145 zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols);
10146 if( !zIdx ){
10147 rc = SQLITE_NOMEM;
10148 }else{
10149 rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg);
10150 idxHashAdd(&rc, &p->hIdx, zName, zIdx);
 
 
 
 
10151 }
10152 sqlite3_free(zName);
10153 sqlite3_free(zIdx);
10154 }
10155 }
@@ -11069,10 +11157,14 @@
11069 rc = idxProcessTriggers(p, pzErr);
11070
11071 /* Create candidate indexes within the in-memory database file */
11072 if( rc==SQLITE_OK ){
11073 rc = idxCreateCandidates(p);
 
 
 
 
11074 }
11075
11076 /* Generate the stat1 data */
11077 if( rc==SQLITE_OK ){
11078 rc = idxPopulateStat1(p, pzErr);
@@ -12161,11 +12253,11 @@
12161 #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */
12162 #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
12163 #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
12164 #define SHFLG_CountChanges 0x00000020 /* .changes setting */
12165 #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */
12166 #define SHFLG_HeaderSet 0x00000080 /* .header has been used */
12167 #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */
12168 #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */
12169
12170 /*
12171 ** Macros for testing and setting shellFlgs
@@ -20197,11 +20289,11 @@
20197 }else{
20198 rc = process_input(p);
20199 pclose(p->in);
20200 }
20201 #endif
20202 }else if( notNormalFile(azArg[1]) || (p->in = fopen(azArg[1], "rb"))==0 ){
20203 utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
20204 rc = 1;
20205 }else{
20206 rc = process_input(p);
20207 fclose(p->in);
@@ -21556,59 +21648,114 @@
21556 }
21557 p->bSafeMode = p->bSafeModePersist;
21558 return rc;
21559 }
21560
21561 /*
21562 ** Return TRUE if a semicolon occurs anywhere in the first N characters
21563 ** of string z[].
21564 */
21565 static int line_contains_semicolon(const char *z, int N){
21566 int i;
21567 for(i=0; i<N; i++){ if( z[i]==';' ) return 1; }
21568 return 0;
21569 }
21570
21571 /*
21572 ** Test to see if a line consists entirely of whitespace.
21573 */
21574 static int _all_whitespace(const char *z){
21575 for(; *z; z++){
21576 if( IsSpace(z[0]) ) continue;
21577 if( *z=='/' && z[1]=='*' ){
21578 z += 2;
21579 while( *z && (*z!='*' || z[1]!='/') ){ z++; }
21580 if( *z==0 ) return 0;
21581 z++;
21582 continue;
21583 }
21584 if( *z=='-' && z[1]=='-' ){
21585 z += 2;
21586 while( *z && *z!='\n' ){ z++; }
21587 if( *z==0 ) return 1;
21588 continue;
21589 }
21590 return 0;
21591 }
21592 return 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21593 }
21594
21595 /*
21596 ** Return TRUE if the line typed in is an SQL command terminator other
21597 ** than a semi-colon. The SQL Server style "go" command is understood
21598 ** as is the Oracle "/".
21599 */
21600 static int line_is_command_terminator(const char *zLine){
21601 while( IsSpace(zLine[0]) ){ zLine++; };
21602 if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){
21603 return 1; /* Oracle */
21604 }
21605 if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o'
21606 && _all_whitespace(&zLine[2]) ){
21607 return 1; /* SQL Server */
21608 }
21609 return 0;
21610 }
21611
21612 /*
21613 ** We need a default sqlite3_complete() implementation to use in case
21614 ** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes
@@ -21661,12 +21808,15 @@
21661 }else{
21662 utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db));
21663 }
21664 return 1;
21665 }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
21666 raw_printf(p->out, "changes: %3lld total_changes: %lld\n",
 
 
21667 sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
 
21668 }
21669 return 0;
21670 }
21671
21672
@@ -21683,14 +21833,14 @@
21683 char *zLine = 0; /* A single input line */
21684 char *zSql = 0; /* Accumulated SQL text */
21685 int nLine; /* Length of current line */
21686 int nSql = 0; /* Bytes of zSql[] used */
21687 int nAlloc = 0; /* Allocated zSql[] space */
21688 int nSqlPrior = 0; /* Bytes of zSql[] used by prior line */
21689 int rc; /* Error code */
21690 int errCnt = 0; /* Number of errors seen */
21691 int startline = 0; /* Line number for start of current input */
 
21692
21693 p->lineno = 0;
21694 while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
21695 fflush(p->out);
21696 zLine = one_input_line(p->in, zLine, nSql>0);
@@ -21702,12 +21852,20 @@
21702 if( seenInterrupt ){
21703 if( p->in!=0 ) break;
21704 seenInterrupt = 0;
21705 }
21706 p->lineno++;
21707 if( nSql==0 && _all_whitespace(zLine) ){
21708 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
 
 
 
 
 
 
 
 
21709 continue;
21710 }
21711 if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
21712 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
21713 if( zLine[0]=='.' ){
@@ -21718,20 +21876,17 @@
21718 errCnt++;
21719 }
21720 }
21721 continue;
21722 }
21723 if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){
21724 memcpy(zLine,";",2);
21725 }
21726 nLine = strlen30(zLine);
21727 if( nSql+nLine+2>=nAlloc ){
21728 nAlloc = nSql+nLine+100;
 
21729 zSql = realloc(zSql, nAlloc);
21730 if( zSql==0 ) shell_out_of_memory();
21731 }
21732 nSqlPrior = nSql;
21733 if( nSql==0 ){
21734 int i;
21735 for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
21736 assert( nAlloc>0 && zSql!=0 );
21737 memcpy(zSql, zLine+i, nLine+1-i);
@@ -21740,27 +21895,26 @@
21740 }else{
21741 zSql[nSql++] = '\n';
21742 memcpy(zSql+nSql, zLine, nLine+1);
21743 nSql += nLine;
21744 }
21745 if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior)
21746 && sqlite3_complete(zSql) ){
21747 errCnt += runOneSqlLine(p, zSql, p->in, startline);
21748 nSql = 0;
21749 if( p->outCount ){
21750 output_reset(p);
21751 p->outCount = 0;
21752 }else{
21753 clearTempFile(p);
21754 }
21755 p->bSafeMode = p->bSafeModePersist;
21756 }else if( nSql && _all_whitespace(zSql) ){
21757 if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
21758 nSql = 0;
21759 }
21760 }
21761 if( nSql && !_all_whitespace(zSql) ){
21762 errCnt += runOneSqlLine(p, zSql, p->in, startline);
21763 }
21764 free(zSql);
21765 free(zLine);
21766 return errCnt>0;
@@ -22399,12 +22553,14 @@
22399 }else if( strcmp(z,"-nullvalue")==0 ){
22400 sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
22401 "%s",cmdline_option_value(argc,argv,++i));
22402 }else if( strcmp(z,"-header")==0 ){
22403 data.showHeader = 1;
22404 }else if( strcmp(z,"-noheader")==0 ){
 
22405 data.showHeader = 0;
 
22406 }else if( strcmp(z,"-echo")==0 ){
22407 ShellSetFlag(&data, SHFLG_Echo);
22408 }else if( strcmp(z,"-eqp")==0 ){
22409 data.autoEQP = AUTOEQP_on;
22410 }else if( strcmp(z,"-eqpfull")==0 ){
22411
--- src/shell.c
+++ src/shell.c
@@ -651,23 +651,42 @@
651 }
652 return n;
653 }
654
655 /*
656 ** Return open FILE * if zFile exists, can be opened for read
657 ** and is an ordinary file or a character stream source.
658 ** Otherwise return 0.
659 */
660 static FILE * openChrSource(const char *zFile){
661 #ifdef _WIN32
662 struct _stat x = {0};
663 # define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0)
664 /* On Windows, open first, then check the stream nature. This order
665 ** is necessary because _stat() and sibs, when checking a named pipe,
666 ** effectively break the pipe as its supplier sees it. */
667 FILE *rv = fopen(zFile, "rb");
668 if( rv==0 ) return 0;
669 if( _fstat(_fileno(rv), &x) != 0
670 || !STAT_CHR_SRC(x.st_mode)){
671 fclose(rv);
672 rv = 0;
673 }
674 return rv;
675 #else
676 struct stat x = {0};
677 int rc = stat(zFile, &x);
678 # define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode))
679 if( rc!=0 ) return 0;
680 if( STAT_CHR_SRC(x.st_mode) ){
681 return fopen(zFile, "rb");
682 }else{
683 return 0;
684 }
685 #endif
686 #undef STAT_CHR_SRC
687 }
688
689 /*
690 ** This routine reads a line of text from FILE in, stores
691 ** the text in memory obtained from malloc() and returns a pointer
692 ** to the text. NULL is returned at end of file, or if malloc()
@@ -2202,10 +2221,15 @@
2221 **
2222 ** If a non-NULL value is specified for the optional $dir parameter and
2223 ** $path is a relative path, then $path is interpreted relative to $dir.
2224 ** And the paths returned in the "name" column of the table are also
2225 ** relative to directory $dir.
2226 **
2227 ** Notes on building this extension for Windows:
2228 ** Unless linked statically with the SQLite library, a preprocessor
2229 ** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone
2230 ** DLL form of this extension for WIN32. See its use below for details.
2231 */
2232 /* #include "sqlite3ext.h" */
2233 SQLITE_EXTENSION_INIT1
2234 #include <stdio.h>
2235 #include <string.h>
@@ -2355,10 +2379,26 @@
2379 fileIntervals.HighPart = pFileTime->dwHighDateTime;
2380
2381 return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000;
2382 }
2383
2384
2385 #if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
2386 # /* To allow a standalone DLL, use this next replacement function: */
2387 # undef sqlite3_win32_utf8_to_unicode
2388 # define sqlite3_win32_utf8_to_unicode utf8_to_utf16
2389 #
2390 LPWSTR utf8_to_utf16(const char *z){
2391 int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0);
2392 LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR));
2393 if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) )
2394 return rv;
2395 sqlite3_free(rv);
2396 return 0;
2397 }
2398 #endif
2399
2400 /*
2401 ** This function attempts to normalize the time values found in the stat()
2402 ** buffer to UTC. This is necessary on Win32, where the runtime library
2403 ** appears to return these values as local times.
2404 */
@@ -3128,10 +3168,18 @@
3168 if( rc==SQLITE_OK ){
3169 rc = fsdirRegister(db);
3170 }
3171 return rc;
3172 }
3173
3174 #if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32))
3175 /* To allow a standalone DLL, make test_windirent.c use the same
3176 * redefined SQLite API calls as the above extension code does.
3177 * Just pull in this .c to accomplish this. As a beneficial side
3178 * effect, this extension becomes a single translation unit. */
3179 # include "test_windirent.c"
3180 #endif
3181
3182 /************************* End ../ext/misc/fileio.c ********************/
3183 /************************* Begin ../ext/misc/completion.c ******************/
3184 /*
3185 ** 2017-07-10
@@ -10099,10 +10147,23 @@
10147 idxFinalize(&rc, pIdxList);
10148
10149 *pRc = rc;
10150 return 0;
10151 }
10152
10153 /* Callback for sqlite3_exec() with query with leading count(*) column.
10154 * The first argument is expected to be an int*, referent to be incremented
10155 * if that leading column is not exactly '0'.
10156 */
10157 static int countNonzeros(void* pCount, int nc,
10158 char* azResults[], char* azColumns[]){
10159 (void)azColumns; /* Suppress unused parameter warning */
10160 if( nc>0 && (azResults[0][0]!='0' || azResults[0][1]!=0) ){
10161 *((int *)pCount) += 1;
10162 }
10163 return 0;
10164 }
10165
10166 static int idxCreateFromCons(
10167 sqlite3expert *p,
10168 IdxScan *pScan,
10169 IdxConstraint *pEq,
@@ -10126,30 +10187,57 @@
10187 }
10188
10189 if( rc==SQLITE_OK ){
10190 /* Hash the list of columns to come up with a name for the index */
10191 const char *zTable = pScan->pTab->zName;
10192 int quoteTable = idxIdentifierRequiresQuotes(zTable);
10193 char *zName = 0; /* Index name */
10194 int collisions = 0;
10195 do{
10196 int i;
10197 char *zFind;
10198 for(i=0; zCols[i]; i++){
10199 h += ((h<<3) + zCols[i]);
10200 }
10201 sqlite3_free(zName);
10202 zName = sqlite3_mprintf("%s_idx_%08x", zTable, h);
10203 if( zName==0 ) break;
10204 /* Is is unique among table, view and index names? */
10205 zFmt = "SELECT count(*) FROM sqlite_schema WHERE name=%Q"
10206 " AND type in ('index','table','view')";
10207 zFind = sqlite3_mprintf(zFmt, zName);
10208 i = 0;
10209 rc = sqlite3_exec(dbm, zFind, countNonzeros, &i, 0);
10210 assert(rc==SQLITE_OK);
10211 sqlite3_free(zFind);
10212 if( i==0 ){
10213 collisions = 0;
10214 break;
10215 }
10216 ++collisions;
10217 }while( collisions<50 && zName!=0 );
10218 if( collisions ){
10219 /* This return means "Gave up trying to find a unique index name." */
10220 rc = SQLITE_BUSY_TIMEOUT;
10221 }else if( zName==0 ){
10222 rc = SQLITE_NOMEM;
10223 }else{
10224 if( quoteTable ){
10225 zFmt = "CREATE INDEX \"%w\" ON \"%w\"(%s)";
10226 }else{
10227 zFmt = "CREATE INDEX %s ON %s(%s)";
10228 }
10229 zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols);
10230 if( !zIdx ){
10231 rc = SQLITE_NOMEM;
10232 }else{
10233 rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg);
10234 if( rc!=SQLITE_OK ){
10235 rc = SQLITE_BUSY_TIMEOUT;
10236 }else{
10237 idxHashAdd(&rc, &p->hIdx, zName, zIdx);
10238 }
10239 }
10240 sqlite3_free(zName);
10241 sqlite3_free(zIdx);
10242 }
10243 }
@@ -11069,10 +11157,14 @@
11157 rc = idxProcessTriggers(p, pzErr);
11158
11159 /* Create candidate indexes within the in-memory database file */
11160 if( rc==SQLITE_OK ){
11161 rc = idxCreateCandidates(p);
11162 }else if ( rc==SQLITE_BUSY_TIMEOUT ){
11163 if( pzErr )
11164 *pzErr = sqlite3_mprintf("Cannot find a unique index name to propose.");
11165 return rc;
11166 }
11167
11168 /* Generate the stat1 data */
11169 if( rc==SQLITE_OK ){
11170 rc = idxPopulateStat1(p, pzErr);
@@ -12161,11 +12253,11 @@
12253 #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */
12254 #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
12255 #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
12256 #define SHFLG_CountChanges 0x00000020 /* .changes setting */
12257 #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */
12258 #define SHFLG_HeaderSet 0x00000080 /* showHeader has been specified */
12259 #define SHFLG_DumpDataOnly 0x00000100 /* .dump show data only */
12260 #define SHFLG_DumpNoSys 0x00000200 /* .dump omits system tables */
12261
12262 /*
12263 ** Macros for testing and setting shellFlgs
@@ -20197,11 +20289,11 @@
20289 }else{
20290 rc = process_input(p);
20291 pclose(p->in);
20292 }
20293 #endif
20294 }else if( (p->in = openChrSource(azArg[1]))==0 ){
20295 utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
20296 rc = 1;
20297 }else{
20298 rc = process_input(p);
20299 fclose(p->in);
@@ -21556,59 +21648,114 @@
21648 }
21649 p->bSafeMode = p->bSafeModePersist;
21650 return rc;
21651 }
21652
21653 /* Line scan result and intermediate states (supporting scan resumption)
21654 */
21655 #ifndef CHAR_BIT
21656 # define CHAR_BIT 8
21657 #endif
21658 typedef enum {
21659 QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT,
21660 QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT,
21661 QSS_Start = 0
21662 } QuickScanState;
21663 #define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask))
21664 #define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start)
21665 #define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start)
21666 #define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark)
21667 #define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi)
21668
21669 /*
21670 ** Scan line for classification to guide shell's handling.
21671 ** The scan is resumable for subsequent lines when prior
21672 ** return values are passed as the 2nd argument.
21673 */
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 '-':
21684 if( *zLine!='-' )
21685 break;
21686 while((cin = *++zLine)!=0 )
21687 if( cin=='\n')
21688 goto PlainScan;
21689 return qss;
21690 case ';':
21691 qss |= QSS_EndingSemi;
21692 continue;
21693 case '/':
21694 if( *zLine=='*' ){
21695 ++zLine;
21696 cWait = '*';
21697 qss = QSS_SETV(qss, cWait);
21698 goto TermScan;
21699 }
21700 break;
21701 case '[':
21702 cin = ']';
21703 /* fall thru */
21704 case '`': case '\'': case '"':
21705 cWait = cin;
21706 qss = QSS_HasDark | cWait;
21707 goto TermScan;
21708 default:
21709 break;
21710 }
21711 qss = (qss & ~QSS_EndingSemi) | QSS_HasDark;
21712 }
21713 }else{
21714 TermScan:
21715 while( (cin = *zLine++)!=0 ){
21716 if( cin==cWait ){
21717 switch( cWait ){
21718 case '*':
21719 if( *zLine != '/' )
21720 continue;
21721 ++zLine;
21722 cWait = 0;
21723 qss = QSS_SETV(qss, 0);
21724 goto PlainScan;
21725 case '`': case '\'': case '"':
21726 if(*zLine==cWait){
21727 ++zLine;
21728 continue;
21729 }
21730 /* fall thru */
21731 case ']':
21732 cWait = 0;
21733 qss = QSS_SETV(qss, 0);
21734 goto PlainScan;
21735 default: assert(0);
21736 }
21737 }
21738 }
21739 }
21740 return qss;
21741 }
21742
21743 /*
21744 ** Return TRUE if the line typed in is an SQL command terminator other
21745 ** than a semi-colon. The SQL Server style "go" command is understood
21746 ** as is the Oracle "/".
21747 */
21748 static int line_is_command_terminator(char *zLine){
21749 while( IsSpace(zLine[0]) ){ zLine++; };
21750 if( zLine[0]=='/' )
21751 zLine += 1; /* Oracle */
21752 else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
21753 zLine += 2; /* SQL Server */
21754 else
21755 return 0;
21756 return quickscan(zLine,QSS_Start)==QSS_Start;
 
21757 }
21758
21759 /*
21760 ** We need a default sqlite3_complete() implementation to use in case
21761 ** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes
@@ -21661,12 +21808,15 @@
21808 }else{
21809 utf8_printf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db));
21810 }
21811 return 1;
21812 }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
21813 char zLineBuf[2000];
21814 sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
21815 "changes: %lld total_changes: %lld",
21816 sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
21817 raw_printf(p->out, "%s\n", zLineBuf);
21818 }
21819 return 0;
21820 }
21821
21822
@@ -21683,14 +21833,14 @@
21833 char *zLine = 0; /* A single input line */
21834 char *zSql = 0; /* Accumulated SQL text */
21835 int nLine; /* Length of current line */
21836 int nSql = 0; /* Bytes of zSql[] used */
21837 int nAlloc = 0; /* Allocated zSql[] space */
 
21838 int rc; /* Error code */
21839 int errCnt = 0; /* Number of errors seen */
21840 int startline = 0; /* Line number for start of current input */
21841 QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
21842
21843 p->lineno = 0;
21844 while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
21845 fflush(p->out);
21846 zLine = one_input_line(p->in, zLine, nSql>0);
@@ -21702,12 +21852,20 @@
21852 if( seenInterrupt ){
21853 if( p->in!=0 ) break;
21854 seenInterrupt = 0;
21855 }
21856 p->lineno++;
21857 if( QSS_INPLAIN(qss)
21858 && line_is_command_terminator(zLine)
21859 && line_is_complete(zSql, nSql) ){
21860 memcpy(zLine,";",2);
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]=='.' ){
@@ -21718,20 +21876,17 @@
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);
21886 if( zSql==0 ) shell_out_of_memory();
21887 }
 
21888 if( nSql==0 ){
21889 int i;
21890 for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
21891 assert( nAlloc>0 && zSql!=0 );
21892 memcpy(zSql, zLine+i, nLine+1-i);
@@ -21740,27 +21895,26 @@
21895 }else{
21896 zSql[nSql++] = '\n';
21897 memcpy(zSql+nSql, zLine, nLine+1);
21898 nSql += nLine;
21899 }
21900 if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
 
21901 errCnt += runOneSqlLine(p, zSql, p->in, startline);
21902 nSql = 0;
21903 if( p->outCount ){
21904 output_reset(p);
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 free(zSql);
21919 free(zLine);
21920 return errCnt>0;
@@ -22399,12 +22553,14 @@
22553 }else if( strcmp(z,"-nullvalue")==0 ){
22554 sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
22555 "%s",cmdline_option_value(argc,argv,++i));
22556 }else if( strcmp(z,"-header")==0 ){
22557 data.showHeader = 1;
22558 ShellSetFlag(&data, SHFLG_HeaderSet);
22559 }else if( strcmp(z,"-noheader")==0 ){
22560 data.showHeader = 0;
22561 ShellSetFlag(&data, SHFLG_HeaderSet);
22562 }else if( strcmp(z,"-echo")==0 ){
22563 ShellSetFlag(&data, SHFLG_Echo);
22564 }else if( strcmp(z,"-eqp")==0 ){
22565 data.autoEQP = AUTOEQP_on;
22566 }else if( strcmp(z,"-eqpfull")==0 ){
22567
+257 -137
--- 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-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1"
457
+#define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
458458
459459
/*
460460
** CAPI3REF: Run-Time Library Version Numbers
461461
** KEYWORDS: sqlite3_version sqlite3_sourceid
462462
**
@@ -19842,11 +19842,11 @@
1984219842
#ifdef SQLITE_TEST
1984319843
SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*);
1984419844
#endif
1984519845
1984619846
#ifdef SQLITE_OMIT_VIRTUALTABLE
19847
-# define sqlite3VtabClear(Y)
19847
+# define sqlite3VtabClear(D,T)
1984819848
# define sqlite3VtabSync(X,Y) SQLITE_OK
1984919849
# define sqlite3VtabRollback(X)
1985019850
# define sqlite3VtabCommit(X)
1985119851
# define sqlite3VtabInSync(db) 0
1985219852
# define sqlite3VtabLock(X)
@@ -48856,11 +48856,11 @@
4885648856
MemStore *p = 0;
4885748857
int szName;
4885848858
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
4885948859
return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags);
4886048860
}
48861
- memset(pFile, 0, sizeof(*p));
48861
+ memset(pFile, 0, sizeof(*pFile));
4886248862
szName = sqlite3Strlen30(zName);
4886348863
if( szName>1 && zName[0]=='/' ){
4886448864
int i;
4886548865
#ifndef SQLITE_MUTEX_OMIT
4886648866
sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
@@ -53160,12 +53160,12 @@
5316053160
5316153161
u16 nExtra; /* Add this many bytes to each in-memory page */
5316253162
i16 nReserve; /* Number of unused bytes at end of each page */
5316353163
u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
5316453164
u32 sectorSize; /* Assumed sector size during rollback */
53165
- int pageSize; /* Number of bytes in a page */
5316653165
Pgno mxPgno; /* Maximum allowed size of the database */
53166
+ i64 pageSize; /* Number of bytes in a page */
5316753167
i64 journalSizeLimit; /* Size limit for persistent journal files */
5316853168
char *zFilename; /* Name of the database file */
5316953169
char *zJournal; /* Name of the journal file */
5317053170
int (*xBusyHandler)(void*); /* Function to call when busy */
5317153171
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
@@ -59218,12 +59218,12 @@
5921859218
/*
5921959219
** Return the approximate number of bytes of memory currently
5922059220
** used by the pager and its associated cache.
5922159221
*/
5922259222
SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){
59223
- int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr)
59224
- + 5*sizeof(void*);
59223
+ int perPageSize = pPager->pageSize + pPager->nExtra
59224
+ + (int)(sizeof(PgHdr) + 5*sizeof(void*));
5922559225
return perPageSize*sqlite3PcachePagecount(pPager->pPCache)
5922659226
+ sqlite3MallocSize(pPager)
5922759227
+ pPager->pageSize;
5922859228
}
5922959229
@@ -59413,18 +59413,18 @@
5941359413
for(ii=nNew; ii<pPager->nSavepoint; ii++){
5941459414
sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
5941559415
}
5941659416
pPager->nSavepoint = nNew;
5941759417
59418
- /* If this is a release of the outermost savepoint, truncate
59419
- ** the sub-journal to zero bytes in size. */
59418
+ /* Truncate the sub-journal so that it only includes the parts
59419
+ ** that are still in use. */
5942059420
if( op==SAVEPOINT_RELEASE ){
5942159421
PagerSavepoint *pRel = &pPager->aSavepoint[nNew];
5942259422
if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){
5942359423
/* Only truncate if it is an in-memory sub-journal. */
5942459424
if( sqlite3JournalIsInMemory(pPager->sjfd) ){
59425
- i64 sz = (pPager->pageSize+4)*pRel->iSubRec;
59425
+ i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec;
5942659426
rc = sqlite3OsTruncate(pPager->sjfd, sz);
5942759427
assert( rc==SQLITE_OK );
5942859428
}
5942959429
pPager->nSubRec = pRel->iSubRec;
5943059430
}
@@ -72713,10 +72713,11 @@
7271372713
nCell -= nTail;
7271472714
}
7271572715
7271672716
pData = &aData[get2byteNotZero(&aData[hdr+5])];
7271772717
if( pData<pBegin ) goto editpage_fail;
72718
+ if( NEVER(pData>pPg->aDataEnd) ) goto editpage_fail;
7271872719
7271972720
/* Add cells to the start of the page */
7272072721
if( iNew<iOld ){
7272172722
int nAdd = MIN(nNew,iOld-iNew);
7272272723
assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB );
@@ -74118,11 +74119,11 @@
7411874119
pBt = pPage->pBt;
7411974120
ovflPageSize = pBt->usableSize - 4;
7412074121
do{
7412174122
rc = btreeGetPage(pBt, ovflPgno, &pPage, 0);
7412274123
if( rc ) return rc;
74123
- if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){
74124
+ if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){
7412474125
rc = SQLITE_CORRUPT_BKPT;
7412574126
}else{
7412674127
if( iOffset+ovflPageSize<(u32)nTotal ){
7412774128
ovflPgno = get4byte(pPage->aData);
7412874129
}else{
@@ -94795,10 +94796,15 @@
9479594796
rc = SQLITE_NOMEM_BKPT;
9479694797
}else if( rc==SQLITE_IOERR_CORRUPTFS ){
9479794798
rc = SQLITE_CORRUPT_BKPT;
9479894799
}
9479994800
assert( rc );
94801
+#ifdef SQLITE_DEBUG
94802
+ if( db->flags & SQLITE_VdbeTrace ){
94803
+ printf("ABORT-due-to-error. rc=%d\n", rc);
94804
+ }
94805
+#endif
9480094806
if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){
9480194807
sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
9480294808
}
9480394809
p->rc = rc;
9480494810
sqlite3SystemError(db, rc);
@@ -105161,10 +105167,11 @@
105161105167
105162105168
/***********************************************************************
105163105169
** Test-only SQL functions that are only usable if enabled
105164105170
** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS
105165105171
*/
105172
+#if !defined(SQLITE_UNTESTABLE)
105166105173
case INLINEFUNC_expr_compare: {
105167105174
/* Compare two expressions using sqlite3ExprCompare() */
105168105175
assert( nFarg==2 );
105169105176
sqlite3VdbeAddOp2(v, OP_Integer,
105170105177
sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1),
@@ -105194,11 +105201,10 @@
105194105201
sqlite3VdbeAddOp2(v, OP_Null, 0, target);
105195105202
}
105196105203
break;
105197105204
}
105198105205
105199
-#ifdef SQLITE_DEBUG
105200105206
case INLINEFUNC_affinity: {
105201105207
/* The AFFINITY() function evaluates to a string that describes
105202105208
** the type affinity of the argument. This is used for testing of
105203105209
** the SQLite type logic.
105204105210
*/
@@ -105208,11 +105214,11 @@
105208105214
aff = sqlite3ExprAffinity(pFarg->a[0].pExpr);
105209105215
sqlite3VdbeLoadString(v, target,
105210105216
(aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]);
105211105217
break;
105212105218
}
105213
-#endif
105219
+#endif /* !defined(SQLITE_UNTESTABLE) */
105214105220
}
105215105221
return target;
105216105222
}
105217105223
105218105224
@@ -108172,12 +108178,11 @@
108172108178
bQuote = sqlite3Isquote(pNew->z[0]);
108173108179
sqlite3NestedParse(pParse,
108174108180
"UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET "
108175108181
"sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
108176108182
"WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' "
108177
- " AND (type != 'index' OR tbl_name = %Q)"
108178
- " AND sql NOT LIKE 'create virtual%%'",
108183
+ " AND (type != 'index' OR tbl_name = %Q)",
108179108184
zDb,
108180108185
zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1,
108181108186
pTab->zName
108182108187
);
108183108188
@@ -109023,11 +109028,11 @@
109023109028
rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc);
109024109029
if( rc==SQLITE_OK ){
109025109030
sqlite3WalkSelect(&sWalker, pSelect);
109026109031
}
109027109032
if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
109028
- }else if( ALWAYS(IsOrdinaryTable(sParse.pNewTable)) ){
109033
+ }else if( IsOrdinaryTable(sParse.pNewTable) ){
109029109034
/* A regular table */
109030109035
int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName);
109031109036
FKey *pFKey;
109032109037
sCtx.pTab = sParse.pNewTable;
109033109038
if( bFKOnly==0 ){
@@ -120209,13 +120214,13 @@
120209120214
}
120210120215
120211120216
/*
120212120217
** Implementation of the changes() SQL function.
120213120218
**
120214
-** IMP: R-62073-11209 The changes() SQL function is a wrapper
120215
-** around the sqlite3_changes64() C/C++ function and hence follows the same
120216
-** rules for counting changes.
120219
+** IMP: R-32760-32347 The changes() SQL function is a wrapper
120220
+** around the sqlite3_changes64() C/C++ function and hence follows the
120221
+** same rules for counting changes.
120217120222
*/
120218120223
static void changes(
120219120224
sqlite3_context *context,
120220120225
int NotUsed,
120221120226
sqlite3_value **NotUsed2
@@ -120234,12 +120239,12 @@
120234120239
int NotUsed,
120235120240
sqlite3_value **NotUsed2
120236120241
){
120237120242
sqlite3 *db = sqlite3_context_db_handle(context);
120238120243
UNUSED_PARAMETER2(NotUsed, NotUsed2);
120239
- /* IMP: R-52756-41993 This function was a wrapper around the
120240
- ** sqlite3_total_changes() C/C++ interface. */
120244
+ /* IMP: R-11217-42568 This function is a wrapper around the
120245
+ ** sqlite3_total_changes64() C/C++ interface. */
120241120246
sqlite3_result_int64(context, sqlite3_total_changes64(db));
120242120247
}
120243120248
120244120249
/*
120245120250
** A structure defining how to do GLOB-style comparisons.
@@ -121761,16 +121766,16 @@
121761121766
**
121762121767
** For peak efficiency, put the most frequently used function last.
121763121768
*/
121764121769
static FuncDef aBuiltinFunc[] = {
121765121770
/***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/
121771
+#if !defined(SQLITE_UNTESTABLE)
121766121772
TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0),
121767121773
TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0),
121768121774
TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0),
121769
-#ifdef SQLITE_DEBUG
121770
- TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0),
121771
-#endif
121775
+ TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0),
121776
+#endif /* !defined(SQLITE_UNTESTABLE) */
121772121777
/***** Regular functions *****/
121773121778
#ifdef SQLITE_SOUNDEX
121774121779
FUNCTION(soundex, 1, 0, 0, soundexFunc ),
121775121780
#endif
121776121781
#ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -128264,17 +128269,18 @@
128264128269
#define PragTyp_SECURE_DELETE 33
128265128270
#define PragTyp_SHRINK_MEMORY 34
128266128271
#define PragTyp_SOFT_HEAP_LIMIT 35
128267128272
#define PragTyp_SYNCHRONOUS 36
128268128273
#define PragTyp_TABLE_INFO 37
128269
-#define PragTyp_TEMP_STORE 38
128270
-#define PragTyp_TEMP_STORE_DIRECTORY 39
128271
-#define PragTyp_THREADS 40
128272
-#define PragTyp_WAL_AUTOCHECKPOINT 41
128273
-#define PragTyp_WAL_CHECKPOINT 42
128274
-#define PragTyp_LOCK_STATUS 43
128275
-#define PragTyp_STATS 44
128274
+#define PragTyp_TABLE_LIST 38
128275
+#define PragTyp_TEMP_STORE 39
128276
+#define PragTyp_TEMP_STORE_DIRECTORY 40
128277
+#define PragTyp_THREADS 41
128278
+#define PragTyp_WAL_AUTOCHECKPOINT 42
128279
+#define PragTyp_WAL_CHECKPOINT 43
128280
+#define PragTyp_LOCK_STATUS 44
128281
+#define PragTyp_STATS 45
128276128282
128277128283
/* Property flags associated with various pragma. */
128278128284
#define PragFlg_NeedSchema 0x01 /* Force schema load before running */
128279128285
#define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */
128280128286
#define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */
@@ -128303,49 +128309,55 @@
128303128309
/* 11 */ "notnull",
128304128310
/* 12 */ "dflt_value",
128305128311
/* 13 */ "pk",
128306128312
/* 14 */ "hidden",
128307128313
/* table_info reuses 8 */
128308
- /* 15 */ "seqno", /* Used by: index_xinfo */
128309
- /* 16 */ "cid",
128310
- /* 17 */ "name",
128311
- /* 18 */ "desc",
128312
- /* 19 */ "coll",
128313
- /* 20 */ "key",
128314
- /* 21 */ "name", /* Used by: function_list */
128315
- /* 22 */ "builtin",
128316
- /* 23 */ "type",
128317
- /* 24 */ "enc",
128318
- /* 25 */ "narg",
128319
- /* 26 */ "flags",
128320
- /* 27 */ "tbl", /* Used by: stats */
128321
- /* 28 */ "idx",
128322
- /* 29 */ "wdth",
128323
- /* 30 */ "hght",
128324
- /* 31 */ "flgs",
128325
- /* 32 */ "seq", /* Used by: index_list */
128326
- /* 33 */ "name",
128327
- /* 34 */ "unique",
128328
- /* 35 */ "origin",
128329
- /* 36 */ "partial",
128330
- /* 37 */ "table", /* Used by: foreign_key_check */
128331
- /* 38 */ "rowid",
128332
- /* 39 */ "parent",
128333
- /* 40 */ "fkid",
128334
- /* index_info reuses 15 */
128335
- /* 41 */ "seq", /* Used by: database_list */
128336
- /* 42 */ "name",
128337
- /* 43 */ "file",
128338
- /* 44 */ "busy", /* Used by: wal_checkpoint */
128339
- /* 45 */ "log",
128340
- /* 46 */ "checkpointed",
128341
- /* collation_list reuses 32 */
128342
- /* 47 */ "database", /* Used by: lock_status */
128343
- /* 48 */ "status",
128344
- /* 49 */ "cache_size", /* Used by: default_cache_size */
128314
+ /* 15 */ "schema", /* Used by: table_list */
128315
+ /* 16 */ "name",
128316
+ /* 17 */ "type",
128317
+ /* 18 */ "ncol",
128318
+ /* 19 */ "wr",
128319
+ /* 20 */ "strict",
128320
+ /* 21 */ "seqno", /* Used by: index_xinfo */
128321
+ /* 22 */ "cid",
128322
+ /* 23 */ "name",
128323
+ /* 24 */ "desc",
128324
+ /* 25 */ "coll",
128325
+ /* 26 */ "key",
128326
+ /* 27 */ "name", /* Used by: function_list */
128327
+ /* 28 */ "builtin",
128328
+ /* 29 */ "type",
128329
+ /* 30 */ "enc",
128330
+ /* 31 */ "narg",
128331
+ /* 32 */ "flags",
128332
+ /* 33 */ "tbl", /* Used by: stats */
128333
+ /* 34 */ "idx",
128334
+ /* 35 */ "wdth",
128335
+ /* 36 */ "hght",
128336
+ /* 37 */ "flgs",
128337
+ /* 38 */ "seq", /* Used by: index_list */
128338
+ /* 39 */ "name",
128339
+ /* 40 */ "unique",
128340
+ /* 41 */ "origin",
128341
+ /* 42 */ "partial",
128342
+ /* 43 */ "table", /* Used by: foreign_key_check */
128343
+ /* 44 */ "rowid",
128344
+ /* 45 */ "parent",
128345
+ /* 46 */ "fkid",
128346
+ /* index_info reuses 21 */
128347
+ /* 47 */ "seq", /* Used by: database_list */
128348
+ /* 48 */ "name",
128349
+ /* 49 */ "file",
128350
+ /* 50 */ "busy", /* Used by: wal_checkpoint */
128351
+ /* 51 */ "log",
128352
+ /* 52 */ "checkpointed",
128353
+ /* collation_list reuses 38 */
128354
+ /* 53 */ "database", /* Used by: lock_status */
128355
+ /* 54 */ "status",
128356
+ /* 55 */ "cache_size", /* Used by: default_cache_size */
128345128357
/* module_list pragma_list reuses 9 */
128346
- /* 50 */ "timeout", /* Used by: busy_timeout */
128358
+ /* 56 */ "timeout", /* Used by: busy_timeout */
128347128359
};
128348128360
128349128361
/* Definitions of all built-in pragmas */
128350128362
typedef struct PragmaName {
128351128363
const char *const zName; /* Name of pragma */
@@ -128392,11 +128404,11 @@
128392128404
#endif
128393128405
#endif
128394128406
{/* zName: */ "busy_timeout",
128395128407
/* ePragTyp: */ PragTyp_BUSY_TIMEOUT,
128396128408
/* ePragFlg: */ PragFlg_Result0,
128397
- /* ColNames: */ 50, 1,
128409
+ /* ColNames: */ 56, 1,
128398128410
/* iArg: */ 0 },
128399128411
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128400128412
{/* zName: */ "cache_size",
128401128413
/* ePragTyp: */ PragTyp_CACHE_SIZE,
128402128414
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
@@ -128431,11 +128443,11 @@
128431128443
#endif
128432128444
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128433128445
{/* zName: */ "collation_list",
128434128446
/* ePragTyp: */ PragTyp_COLLATION_LIST,
128435128447
/* ePragFlg: */ PragFlg_Result0,
128436
- /* ColNames: */ 32, 2,
128448
+ /* ColNames: */ 38, 2,
128437128449
/* iArg: */ 0 },
128438128450
#endif
128439128451
#if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
128440128452
{/* zName: */ "compile_options",
128441128453
/* ePragTyp: */ PragTyp_COMPILE_OPTIONS,
@@ -128466,18 +128478,18 @@
128466128478
#endif
128467128479
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128468128480
{/* zName: */ "database_list",
128469128481
/* ePragTyp: */ PragTyp_DATABASE_LIST,
128470128482
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
128471
- /* ColNames: */ 41, 3,
128483
+ /* ColNames: */ 47, 3,
128472128484
/* iArg: */ 0 },
128473128485
#endif
128474128486
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
128475128487
{/* zName: */ "default_cache_size",
128476128488
/* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE,
128477128489
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
128478
- /* ColNames: */ 49, 1,
128490
+ /* ColNames: */ 55, 1,
128479128491
/* iArg: */ 0 },
128480128492
#endif
128481128493
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128482128494
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128483128495
{/* zName: */ "defer_foreign_keys",
@@ -128503,11 +128515,11 @@
128503128515
#endif
128504128516
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128505128517
{/* zName: */ "foreign_key_check",
128506128518
/* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK,
128507128519
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt,
128508
- /* ColNames: */ 37, 4,
128520
+ /* ColNames: */ 43, 4,
128509128521
/* iArg: */ 0 },
128510128522
#endif
128511128523
#if !defined(SQLITE_OMIT_FOREIGN_KEY)
128512128524
{/* zName: */ "foreign_key_list",
128513128525
/* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST,
@@ -128546,11 +128558,11 @@
128546128558
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128547128559
#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
128548128560
{/* zName: */ "function_list",
128549128561
/* ePragTyp: */ PragTyp_FUNCTION_LIST,
128550128562
/* ePragFlg: */ PragFlg_Result0,
128551
- /* ColNames: */ 21, 6,
128563
+ /* ColNames: */ 27, 6,
128552128564
/* iArg: */ 0 },
128553128565
#endif
128554128566
#endif
128555128567
{/* zName: */ "hard_heap_limit",
128556128568
/* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT,
@@ -128575,21 +128587,21 @@
128575128587
#endif
128576128588
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128577128589
{/* zName: */ "index_info",
128578128590
/* ePragTyp: */ PragTyp_INDEX_INFO,
128579128591
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128580
- /* ColNames: */ 15, 3,
128592
+ /* ColNames: */ 21, 3,
128581128593
/* iArg: */ 0 },
128582128594
{/* zName: */ "index_list",
128583128595
/* ePragTyp: */ PragTyp_INDEX_LIST,
128584128596
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128585
- /* ColNames: */ 32, 5,
128597
+ /* ColNames: */ 38, 5,
128586128598
/* iArg: */ 0 },
128587128599
{/* zName: */ "index_xinfo",
128588128600
/* ePragTyp: */ PragTyp_INDEX_INFO,
128589128601
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128590
- /* ColNames: */ 15, 6,
128602
+ /* ColNames: */ 21, 6,
128591128603
/* iArg: */ 1 },
128592128604
#endif
128593128605
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
128594128606
{/* zName: */ "integrity_check",
128595128607
/* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
@@ -128625,11 +128637,11 @@
128625128637
#endif
128626128638
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
128627128639
{/* zName: */ "lock_status",
128628128640
/* ePragTyp: */ PragTyp_LOCK_STATUS,
128629128641
/* ePragFlg: */ PragFlg_Result0,
128630
- /* ColNames: */ 47, 2,
128642
+ /* ColNames: */ 53, 2,
128631128643
/* iArg: */ 0 },
128632128644
#endif
128633128645
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128634128646
{/* zName: */ "locking_mode",
128635128647
/* ePragTyp: */ PragTyp_LOCKING_MODE,
@@ -128764,11 +128776,11 @@
128764128776
#endif
128765128777
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG)
128766128778
{/* zName: */ "stats",
128767128779
/* ePragTyp: */ PragTyp_STATS,
128768128780
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
128769
- /* ColNames: */ 27, 5,
128781
+ /* ColNames: */ 33, 5,
128770128782
/* iArg: */ 0 },
128771128783
#endif
128772128784
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128773128785
{/* zName: */ "synchronous",
128774128786
/* ePragTyp: */ PragTyp_SYNCHRONOUS,
@@ -128780,10 +128792,15 @@
128780128792
{/* zName: */ "table_info",
128781128793
/* ePragTyp: */ PragTyp_TABLE_INFO,
128782128794
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128783128795
/* ColNames: */ 8, 6,
128784128796
/* iArg: */ 0 },
128797
+ {/* zName: */ "table_list",
128798
+ /* ePragTyp: */ PragTyp_TABLE_LIST,
128799
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1,
128800
+ /* ColNames: */ 15, 6,
128801
+ /* iArg: */ 1 },
128785128802
{/* zName: */ "table_xinfo",
128786128803
/* ePragTyp: */ PragTyp_TABLE_INFO,
128787128804
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128788128805
/* ColNames: */ 8, 7,
128789128806
/* iArg: */ 1 },
@@ -128855,11 +128872,11 @@
128855128872
/* ColNames: */ 0, 0,
128856128873
/* iArg: */ 0 },
128857128874
{/* zName: */ "wal_checkpoint",
128858128875
/* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
128859128876
/* ePragFlg: */ PragFlg_NeedSchema,
128860
- /* ColNames: */ 44, 3,
128877
+ /* ColNames: */ 50, 3,
128861128878
/* iArg: */ 0 },
128862128879
#endif
128863128880
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128864128881
{/* zName: */ "writable_schema",
128865128882
/* ePragTyp: */ PragTyp_FLAG,
@@ -128866,11 +128883,11 @@
128866128883
/* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
128867128884
/* ColNames: */ 0, 0,
128868128885
/* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError },
128869128886
#endif
128870128887
};
128871
-/* Number of pragmas: 67 on by default, 77 total. */
128888
+/* Number of pragmas: 68 on by default, 78 total. */
128872128889
128873128890
/************** End of pragma.h **********************************************/
128874128891
/************** Continuing where we left off in pragma.c *********************/
128875128892
128876128893
/*
@@ -130035,10 +130052,58 @@
130035130052
}
130036130053
}
130037130054
}
130038130055
break;
130039130056
130057
+ /*
130058
+ ** PRAGMA table_list
130059
+ **
130060
+ ** Return a single row for each table, virtual table, or view in the
130061
+ ** entire schema.
130062
+ **
130063
+ ** schema: Name of attached database hold this table
130064
+ ** name: Name of the table itself
130065
+ ** type: "table", "view", "virtual", "shadow"
130066
+ ** ncol: Number of columns
130067
+ ** wr: True for a WITHOUT ROWID table
130068
+ ** strict: True for a STRICT table
130069
+ */
130070
+ case PragTyp_TABLE_LIST: {
130071
+ int ii;
130072
+ pParse->nMem = 6;
130073
+ sqlite3CodeVerifyNamedSchema(pParse, zDb);
130074
+ for(ii=0; ii<db->nDb; ii++){
130075
+ HashElem *k;
130076
+ Hash *pHash;
130077
+ if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue;
130078
+ pHash = &db->aDb[ii].pSchema->tblHash;
130079
+ for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){
130080
+ Table *pTab = sqliteHashData(k);
130081
+ const char *zType;
130082
+ if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue;
130083
+ if( IsView(pTab) ){
130084
+ zType = "view";
130085
+ }else if( IsVirtual(pTab) ){
130086
+ zType = "virtual";
130087
+ }else if( pTab->tabFlags & TF_Shadow ){
130088
+ zType = "shadow";
130089
+ }else{
130090
+ zType = "table";
130091
+ }
130092
+ sqlite3VdbeMultiLoad(v, 1, "sssiii",
130093
+ db->aDb[ii].zDbSName,
130094
+ pTab->zName,
130095
+ zType,
130096
+ pTab->nCol,
130097
+ (pTab->tabFlags & TF_WithoutRowid)!=0,
130098
+ (pTab->tabFlags & TF_Strict)!=0
130099
+ );
130100
+ }
130101
+ }
130102
+ }
130103
+ break;
130104
+
130040130105
#ifdef SQLITE_DEBUG
130041130106
case PragTyp_STATS: {
130042130107
Index *pIdx;
130043130108
HashElem *i;
130044130109
pParse->nMem = 5;
@@ -130544,11 +130609,13 @@
130544130609
}else{
130545130610
integrityCheckResultRow(v);
130546130611
}
130547130612
sqlite3VdbeJumpHere(v, jmp2);
130548130613
}
130549
- if( pTab->tabFlags & TF_Strict ){
130614
+ if( (pTab->tabFlags & TF_Strict)!=0
130615
+ && pCol->eCType!=COLTYPE_ANY
130616
+ ){
130550130617
jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0,
130551130618
sqlite3StdTypeMap[pCol->eCType-1]);
130552130619
VdbeCoverage(v);
130553130620
zErr = sqlite3MPrintf(db, "non-%s value in %s.%s",
130554130621
sqlite3StdType[pCol->eCType-1],
@@ -132836,10 +132903,13 @@
132836132903
132837132904
pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
132838132905
pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
132839132906
132840132907
pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2);
132908
+ assert( pE2!=0 || pEq==0 ); /* Due to db->mallocFailed test
132909
+ ** in sqlite3DbMallocRawNN() called from
132910
+ ** sqlite3PExpr(). */
132841132911
if( pEq && isOuterJoin ){
132842132912
ExprSetProperty(pEq, EP_FromJoin);
132843132913
assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
132844132914
ExprSetVVAProperty(pEq, EP_NoReduce);
132845132915
pEq->iRightJoinTable = pE2->iTable;
@@ -148365,11 +148435,11 @@
148365148435
** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975
148366148436
** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a
148367148437
*/
148368148438
if( pLeft->op!=TK_COLUMN
148369148439
|| sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
148370
- || IsVirtual(pLeft->y.pTab) /* Value might be numeric */
148440
+ || (pLeft->y.pTab && IsVirtual(pLeft->y.pTab)) /* Might be numeric */
148371148441
){
148372148442
int isNum;
148373148443
double rDummy;
148374148444
isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8);
148375148445
if( isNum<=0 ){
@@ -156490,10 +156560,13 @@
156490156560
);
156491156561
SELECTTRACE(1,pParse,pSub,
156492156562
("New window-function subquery in FROM clause of (%u/%p)\n",
156493156563
p->selId, p));
156494156564
p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
156565
+ assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside
156566
+ ** of sqlite3DbMallocRawNN() called from
156567
+ ** sqlite3SrcListAppend() */
156495156568
if( p->pSrc ){
156496156569
Table *pTab2;
156497156570
p->pSrc->a[0].pSelect = pSub;
156498156571
sqlite3SrcListAssignCursors(pParse, p->pSrc);
156499156572
pSub->selFlags |= SF_Expanded|SF_OrderByReqd;
@@ -192908,11 +192981,15 @@
192908192981
#else
192909192982
/* #include "sqlite3.h" */
192910192983
#endif
192911192984
SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
192912192985
192913
-#ifndef SQLITE_AMALGAMATION
192986
+/*
192987
+** If building separately, we will need some setup that is normally
192988
+** found in sqliteInt.h
192989
+*/
192990
+#if !defined(SQLITE_AMALGAMATION)
192914192991
#include "sqlite3rtree.h"
192915192992
typedef sqlite3_int64 i64;
192916192993
typedef sqlite3_uint64 u64;
192917192994
typedef unsigned char u8;
192918192995
typedef unsigned short u16;
@@ -192921,11 +192998,21 @@
192921192998
# define NDEBUG 1
192922192999
#endif
192923193000
#if defined(NDEBUG) && defined(SQLITE_DEBUG)
192924193001
# undef NDEBUG
192925193002
#endif
193003
+#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
193004
+# define ALWAYS(X) (1)
193005
+# define NEVER(X) (0)
193006
+#elif !defined(NDEBUG)
193007
+# define ALWAYS(X) ((X)?1:(assert(0),0))
193008
+# define NEVER(X) ((X)?(assert(0),1):0)
193009
+#else
193010
+# define ALWAYS(X) (X)
193011
+# define NEVER(X) (X)
192926193012
#endif
193013
+#endif /* !defined(SQLITE_AMALGAMATION) */
192927193014
192928193015
/* #include <string.h> */
192929193016
/* #include <stdio.h> */
192930193017
/* #include <assert.h> */
192931193018
/* #include <stdlib.h> */
@@ -192979,11 +193066,13 @@
192979193066
u8 nDim2; /* Twice the number of dimensions */
192980193067
u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
192981193068
u8 nBytesPerCell; /* Bytes consumed per cell */
192982193069
u8 inWrTrans; /* True if inside write transaction */
192983193070
u8 nAux; /* # of auxiliary columns in %_rowid */
193071
+#ifdef SQLITE_ENABLE_GEOPOLY
192984193072
u8 nAuxNotNull; /* Number of initial not-null aux columns */
193073
+#endif
192985193074
#ifdef SQLITE_DEBUG
192986193075
u8 bCorrupt; /* Shadow table corruption detected */
192987193076
#endif
192988193077
int iDepth; /* Current depth of the r-tree structure */
192989193078
char *zDb; /* Name of database containing r-tree table */
@@ -193510,22 +193599,10 @@
193510193599
pRtree->pNodeBlob = 0;
193511193600
sqlite3_blob_close(pBlob);
193512193601
}
193513193602
}
193514193603
193515
-/*
193516
-** Check to see if pNode is the same as pParent or any of the parents
193517
-** of pParent.
193518
-*/
193519
-static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){
193520
- do{
193521
- if( pNode==pParent ) return 1;
193522
- pParent = pParent->pParent;
193523
- }while( pParent );
193524
- return 0;
193525
-}
193526
-
193527193604
/*
193528193605
** Obtain a reference to an r-tree node.
193529193606
*/
193530193607
static int nodeAcquire(
193531193608
Rtree *pRtree, /* R-tree structure */
@@ -193538,18 +193615,11 @@
193538193615
193539193616
/* Check if the requested node is already in the hash table. If so,
193540193617
** increase its reference count and return it.
193541193618
*/
193542193619
if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){
193543
- if( pParent && !pNode->pParent ){
193544
- if( nodeInParentChain(pNode, pParent) ){
193545
- RTREE_IS_CORRUPT(pRtree);
193546
- return SQLITE_CORRUPT_VTAB;
193547
- }
193548
- pParent->nRef++;
193549
- pNode->pParent = pParent;
193550
- }else if( pParent && pNode->pParent && pParent!=pNode->pParent ){
193620
+ if( pParent && pParent!=pNode->pParent ){
193551193621
RTREE_IS_CORRUPT(pRtree);
193552193622
return SQLITE_CORRUPT_VTAB;
193553193623
}
193554193624
pNode->nRef++;
193555193625
*ppNode = pNode;
@@ -193603,11 +193673,11 @@
193603193673
** of the r-tree structure. A height of zero means all data is stored on
193604193674
** the root node. A height of one means the children of the root node
193605193675
** are the leaves, and so on. If the depth as specified on the root node
193606193676
** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt.
193607193677
*/
193608
- if( pNode && rc==SQLITE_OK && iNode==1 ){
193678
+ if( rc==SQLITE_OK && pNode && iNode==1 ){
193609193679
pRtree->iDepth = readInt16(pNode->zData);
193610193680
if( pRtree->iDepth>RTREE_MAX_DEPTH ){
193611193681
rc = SQLITE_CORRUPT_VTAB;
193612193682
RTREE_IS_CORRUPT(pRtree);
193613193683
}
@@ -194209,15 +194279,16 @@
194209194279
** Return the index of the cell containing a pointer to node pNode
194210194280
** in its parent. If pNode is the root node, return -1.
194211194281
*/
194212194282
static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){
194213194283
RtreeNode *pParent = pNode->pParent;
194214
- if( pParent ){
194284
+ if( ALWAYS(pParent) ){
194215194285
return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex);
194286
+ }else{
194287
+ *piIndex = -1;
194288
+ return SQLITE_OK;
194216194289
}
194217
- *piIndex = -1;
194218
- return SQLITE_OK;
194219194290
}
194220194291
194221194292
/*
194222194293
** Compare two search points. Return negative, zero, or positive if the first
194223194294
** is less than, equal to, or greater than the second.
@@ -194336,11 +194407,12 @@
194336194407
if( pCur->bPoint ){
194337194408
int ii;
194338194409
pNew = rtreeEnqueue(pCur, rScore, iLevel);
194339194410
if( pNew==0 ) return 0;
194340194411
ii = (int)(pNew - pCur->aPoint) + 1;
194341
- if( ii<RTREE_CACHE_SZ ){
194412
+ assert( ii==1 );
194413
+ if( ALWAYS(ii<RTREE_CACHE_SZ) ){
194342194414
assert( pCur->aNode[ii]==0 );
194343194415
pCur->aNode[ii] = pCur->aNode[0];
194344194416
}else{
194345194417
nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]);
194346194418
}
@@ -194397,11 +194469,11 @@
194397194469
p->aNode[i] = 0;
194398194470
}
194399194471
if( p->bPoint ){
194400194472
p->anQueue[p->sPoint.iLevel]--;
194401194473
p->bPoint = 0;
194402
- }else if( p->nPoint ){
194474
+ }else if( ALWAYS(p->nPoint) ){
194403194475
p->anQueue[p->aPoint[0].iLevel]--;
194404194476
n = --p->nPoint;
194405194477
p->aPoint[0] = p->aPoint[n];
194406194478
if( n<RTREE_CACHE_SZ-1 ){
194407194479
p->aNode[1] = p->aNode[n+1];
@@ -194538,11 +194610,11 @@
194538194610
static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
194539194611
RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
194540194612
RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
194541194613
int rc = SQLITE_OK;
194542194614
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194543
- if( rc==SQLITE_OK && p ){
194615
+ if( rc==SQLITE_OK && ALWAYS(p) ){
194544194616
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
194545194617
}
194546194618
return rc;
194547194619
}
194548194620
@@ -194556,11 +194628,11 @@
194556194628
RtreeCoord c;
194557194629
int rc = SQLITE_OK;
194558194630
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194559194631
194560194632
if( rc ) return rc;
194561
- if( p==0 ) return SQLITE_OK;
194633
+ if( NEVER(p==0) ) return SQLITE_OK;
194562194634
if( i==0 ){
194563194635
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
194564194636
}else if( i<=pRtree->nDim2 ){
194565194637
nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
194566194638
#ifndef SQLITE_RTREE_INT_ONLY
@@ -194755,12 +194827,15 @@
194755194827
}
194756194828
}
194757194829
}
194758194830
if( rc==SQLITE_OK ){
194759194831
RtreeSearchPoint *pNew;
194832
+ assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */
194760194833
pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
194761
- if( pNew==0 ) return SQLITE_NOMEM;
194834
+ if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */
194835
+ return SQLITE_NOMEM;
194836
+ }
194762194837
pNew->id = 1;
194763194838
pNew->iCell = 0;
194764194839
pNew->eWithin = PARTLY_WITHIN;
194765194840
assert( pCsr->bPoint==1 );
194766194841
pCsr->aNode[0] = pRoot;
@@ -194833,11 +194908,11 @@
194833194908
assert( pIdxInfo->idxStr==0 );
194834194909
for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
194835194910
struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
194836194911
194837194912
if( bMatch==0 && p->usable
194838
- && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
194913
+ && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
194839194914
){
194840194915
/* We have an equality constraint on the rowid. Use strategy 1. */
194841194916
int jj;
194842194917
for(jj=0; jj<ii; jj++){
194843194918
pIdxInfo->aConstraintUsage[jj].argvIndex = 0;
@@ -195086,16 +195161,23 @@
195086195161
RtreeNode *pNode, /* Adjust ancestry of this node. */
195087195162
RtreeCell *pCell /* This cell was just inserted */
195088195163
){
195089195164
RtreeNode *p = pNode;
195090195165
int cnt = 0;
195166
+ int rc;
195091195167
while( p->pParent ){
195092195168
RtreeNode *pParent = p->pParent;
195093195169
RtreeCell cell;
195094195170
int iCell;
195095195171
195096
- if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){
195172
+ cnt++;
195173
+ if( NEVER(cnt>100) ){
195174
+ RTREE_IS_CORRUPT(pRtree);
195175
+ return SQLITE_CORRUPT_VTAB;
195176
+ }
195177
+ rc = nodeParentIndex(pRtree, p, &iCell);
195178
+ if( NEVER(rc!=SQLITE_OK) ){
195097195179
RTREE_IS_CORRUPT(pRtree);
195098195180
return SQLITE_CORRUPT_VTAB;
195099195181
}
195100195182
195101195183
nodeGetCell(pRtree, pParent, iCell, &cell);
@@ -195475,15 +195557,16 @@
195475195557
}
195476195558
}else{
195477195559
RtreeNode *pParent = pLeft->pParent;
195478195560
int iCell;
195479195561
rc = nodeParentIndex(pRtree, pLeft, &iCell);
195480
- if( rc==SQLITE_OK ){
195562
+ if( ALWAYS(rc==SQLITE_OK) ){
195481195563
nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell);
195482195564
rc = AdjustTree(pRtree, pParent, &leftbbox);
195565
+ assert( rc==SQLITE_OK );
195483195566
}
195484
- if( rc!=SQLITE_OK ){
195567
+ if( NEVER(rc!=SQLITE_OK) ){
195485195568
goto splitnode_out;
195486195569
}
195487195570
}
195488195571
if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){
195489195572
goto splitnode_out;
@@ -195554,11 +195637,11 @@
195554195637
** want to do this as it leads to a memory leak when trying to delete
195555195638
** the referenced counted node structures.
195556195639
*/
195557195640
iNode = sqlite3_column_int64(pRtree->pReadParent, 0);
195558195641
for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent);
195559
- if( !pTest ){
195642
+ if( pTest==0 ){
195560195643
rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent);
195561195644
}
195562195645
}
195563195646
rc = sqlite3_reset(pRtree->pReadParent);
195564195647
if( rc==SQLITE_OK ) rc = rc2;
@@ -195585,10 +195668,11 @@
195585195668
rc = nodeParentIndex(pRtree, pNode, &iCell);
195586195669
if( rc==SQLITE_OK ){
195587195670
pParent = pNode->pParent;
195588195671
pNode->pParent = 0;
195589195672
rc = deleteCell(pRtree, pParent, iCell, iHeight+1);
195673
+ testcase( rc!=SQLITE_OK );
195590195674
}
195591195675
rc2 = nodeRelease(pRtree, pParent);
195592195676
if( rc==SQLITE_OK ){
195593195677
rc = rc2;
195594195678
}
@@ -195807,11 +195891,11 @@
195807195891
pRtree->iReinsertHeight = iHeight;
195808195892
rc = Reinsert(pRtree, pNode, pCell, iHeight);
195809195893
}
195810195894
}else{
195811195895
rc = AdjustTree(pRtree, pNode, pCell);
195812
- if( rc==SQLITE_OK ){
195896
+ if( ALWAYS(rc==SQLITE_OK) ){
195813195897
if( iHeight==0 ){
195814195898
rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode);
195815195899
}else{
195816195900
rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode);
195817195901
}
@@ -195913,11 +195997,11 @@
195913195997
*/
195914195998
if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
195915195999
int rc2;
195916196000
RtreeNode *pChild = 0;
195917196001
i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
195918
- rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
196002
+ rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */
195919196003
if( rc==SQLITE_OK ){
195920196004
rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
195921196005
}
195922196006
rc2 = nodeRelease(pRtree, pChild);
195923196007
if( rc==SQLITE_OK ) rc = rc2;
@@ -196248,11 +196332,11 @@
196248196332
static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
196249196333
const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'";
196250196334
char *zSql;
196251196335
sqlite3_stmt *p;
196252196336
int rc;
196253
- i64 nRow = 0;
196337
+ i64 nRow = RTREE_MIN_ROWEST;
196254196338
196255196339
rc = sqlite3_table_column_metadata(
196256196340
db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
196257196341
);
196258196342
if( rc!=SQLITE_OK ){
@@ -196265,24 +196349,14 @@
196265196349
}else{
196266196350
rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0);
196267196351
if( rc==SQLITE_OK ){
196268196352
if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0);
196269196353
rc = sqlite3_finalize(p);
196270
- }else if( rc!=SQLITE_NOMEM ){
196271
- rc = SQLITE_OK;
196272
- }
196273
-
196274
- if( rc==SQLITE_OK ){
196275
- if( nRow==0 ){
196276
- pRtree->nRowEst = RTREE_DEFAULT_ROWEST;
196277
- }else{
196278
- pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
196279
- }
196280196354
}
196281196355
sqlite3_free(zSql);
196282196356
}
196283
-
196357
+ pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
196284196358
return rc;
196285196359
}
196286196360
196287196361
196288196362
/*
@@ -196428,13 +196502,16 @@
196428196502
int ii;
196429196503
char *zSql;
196430196504
sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
196431196505
for(ii=0; ii<pRtree->nAux; ii++){
196432196506
if( ii ) sqlite3_str_append(p, ",", 1);
196507
+#ifdef SQLITE_ENABLE_GEOPOLY
196433196508
if( ii<pRtree->nAuxNotNull ){
196434196509
sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii);
196435
- }else{
196510
+ }else
196511
+#endif
196512
+ {
196436196513
sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
196437196514
}
196438196515
}
196439196516
sqlite3_str_appendf(p, " WHERE rowid=?1");
196440196517
zSql = sqlite3_str_finish(p);
@@ -197109,12 +197186,14 @@
197109197186
if( check.rc==SQLITE_OK ){
197110197187
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
197111197188
if( pStmt ){
197112197189
nAux = sqlite3_column_count(pStmt) - 2;
197113197190
sqlite3_finalize(pStmt);
197191
+ }else
197192
+ if( check.rc!=SQLITE_NOMEM ){
197193
+ check.rc = SQLITE_OK;
197114197194
}
197115
- check.rc = SQLITE_OK;
197116197195
}
197117197196
197118197197
/* Find number of dimensions in the rtree table. */
197119197198
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
197120197199
if( pStmt ){
@@ -199185,11 +199264,14 @@
199185199264
){
199186199265
RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
199187199266
199188199267
/* Allocate and populate the context object. */
199189199268
pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
199190
- if( !pGeomCtx ) return SQLITE_NOMEM;
199269
+ if( !pGeomCtx ){
199270
+ if( xDestructor ) xDestructor(pContext);
199271
+ return SQLITE_NOMEM;
199272
+ }
199191199273
pGeomCtx->xGeom = 0;
199192199274
pGeomCtx->xQueryFunc = xQueryFunc;
199193199275
pGeomCtx->xDestructor = xDestructor;
199194199276
pGeomCtx->pContext = pContext;
199195199277
return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
@@ -222468,10 +222550,46 @@
222468222550
if( p->pStruct!=(Fts5Structure*)pStruct ){
222469222551
return SQLITE_ABORT;
222470222552
}
222471222553
return SQLITE_OK;
222472222554
}
222555
+
222556
+/*
222557
+** Ensure that structure object (*pp) is writable.
222558
+**
222559
+** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If
222560
+** an error occurs, (*pRc) is set to an SQLite error code before returning.
222561
+*/
222562
+static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){
222563
+ Fts5Structure *p = *pp;
222564
+ if( *pRc==SQLITE_OK && p->nRef>1 ){
222565
+ int nByte = sizeof(Fts5Structure)+(p->nLevel-1)*sizeof(Fts5StructureLevel);
222566
+ Fts5Structure *pNew;
222567
+ pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte);
222568
+ if( pNew ){
222569
+ int i;
222570
+ memcpy(pNew, p, nByte);
222571
+ for(i=0; i<p->nLevel; i++) pNew->aLevel[i].aSeg = 0;
222572
+ for(i=0; i<p->nLevel; i++){
222573
+ Fts5StructureLevel *pLvl = &pNew->aLevel[i];
222574
+ nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg;
222575
+ pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte);
222576
+ if( pLvl->aSeg==0 ){
222577
+ for(i=0; i<p->nLevel; i++){
222578
+ sqlite3_free(pNew->aLevel[i].aSeg);
222579
+ }
222580
+ sqlite3_free(pNew);
222581
+ return;
222582
+ }
222583
+ memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte);
222584
+ }
222585
+ p->nRef--;
222586
+ pNew->nRef = 1;
222587
+ }
222588
+ *pp = pNew;
222589
+ }
222590
+}
222473222591
222474222592
/*
222475222593
** Deserialize and return the structure record currently stored in serialized
222476222594
** form within buffer pData/nData.
222477222595
**
@@ -222570,13 +222688,15 @@
222570222688
*ppOut = pRet;
222571222689
return rc;
222572222690
}
222573222691
222574222692
/*
222575
-**
222693
+** Add a level to the Fts5Structure.aLevel[] array of structure object
222694
+** (*ppStruct).
222576222695
*/
222577222696
static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
222697
+ fts5StructureMakeWritable(pRc, ppStruct);
222578222698
if( *pRc==SQLITE_OK ){
222579222699
Fts5Structure *pStruct = *ppStruct;
222580222700
int nLevel = pStruct->nLevel;
222581222701
sqlite3_int64 nByte = (
222582222702
sizeof(Fts5Structure) + /* Main structure */
@@ -231175,11 +231295,11 @@
231175231295
int nArg, /* Number of args */
231176231296
sqlite3_value **apUnused /* Function arguments */
231177231297
){
231178231298
assert( nArg==0 );
231179231299
UNUSED_PARAM2(nArg, apUnused);
231180
- sqlite3_result_text(pCtx, "fts5: 2021-09-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1", -1, SQLITE_TRANSIENT);
231300
+ sqlite3_result_text(pCtx, "fts5: 2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88", -1, SQLITE_TRANSIENT);
231181231301
}
231182231302
231183231303
/*
231184231304
** Return true if zName is the extension on one of the shadow tables used
231185231305
** by this module.
231186231306
--- 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-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1"
458
459 /*
460 ** CAPI3REF: Run-Time Library Version Numbers
461 ** KEYWORDS: sqlite3_version sqlite3_sourceid
462 **
@@ -19842,11 +19842,11 @@
19842 #ifdef SQLITE_TEST
19843 SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*);
19844 #endif
19845
19846 #ifdef SQLITE_OMIT_VIRTUALTABLE
19847 # define sqlite3VtabClear(Y)
19848 # define sqlite3VtabSync(X,Y) SQLITE_OK
19849 # define sqlite3VtabRollback(X)
19850 # define sqlite3VtabCommit(X)
19851 # define sqlite3VtabInSync(db) 0
19852 # define sqlite3VtabLock(X)
@@ -48856,11 +48856,11 @@
48856 MemStore *p = 0;
48857 int szName;
48858 if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
48859 return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags);
48860 }
48861 memset(pFile, 0, sizeof(*p));
48862 szName = sqlite3Strlen30(zName);
48863 if( szName>1 && zName[0]=='/' ){
48864 int i;
48865 #ifndef SQLITE_MUTEX_OMIT
48866 sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
@@ -53160,12 +53160,12 @@
53160
53161 u16 nExtra; /* Add this many bytes to each in-memory page */
53162 i16 nReserve; /* Number of unused bytes at end of each page */
53163 u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
53164 u32 sectorSize; /* Assumed sector size during rollback */
53165 int pageSize; /* Number of bytes in a page */
53166 Pgno mxPgno; /* Maximum allowed size of the database */
 
53167 i64 journalSizeLimit; /* Size limit for persistent journal files */
53168 char *zFilename; /* Name of the database file */
53169 char *zJournal; /* Name of the journal file */
53170 int (*xBusyHandler)(void*); /* Function to call when busy */
53171 void *pBusyHandlerArg; /* Context argument for xBusyHandler */
@@ -59218,12 +59218,12 @@
59218 /*
59219 ** Return the approximate number of bytes of memory currently
59220 ** used by the pager and its associated cache.
59221 */
59222 SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){
59223 int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr)
59224 + 5*sizeof(void*);
59225 return perPageSize*sqlite3PcachePagecount(pPager->pPCache)
59226 + sqlite3MallocSize(pPager)
59227 + pPager->pageSize;
59228 }
59229
@@ -59413,18 +59413,18 @@
59413 for(ii=nNew; ii<pPager->nSavepoint; ii++){
59414 sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
59415 }
59416 pPager->nSavepoint = nNew;
59417
59418 /* If this is a release of the outermost savepoint, truncate
59419 ** the sub-journal to zero bytes in size. */
59420 if( op==SAVEPOINT_RELEASE ){
59421 PagerSavepoint *pRel = &pPager->aSavepoint[nNew];
59422 if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){
59423 /* Only truncate if it is an in-memory sub-journal. */
59424 if( sqlite3JournalIsInMemory(pPager->sjfd) ){
59425 i64 sz = (pPager->pageSize+4)*pRel->iSubRec;
59426 rc = sqlite3OsTruncate(pPager->sjfd, sz);
59427 assert( rc==SQLITE_OK );
59428 }
59429 pPager->nSubRec = pRel->iSubRec;
59430 }
@@ -72713,10 +72713,11 @@
72713 nCell -= nTail;
72714 }
72715
72716 pData = &aData[get2byteNotZero(&aData[hdr+5])];
72717 if( pData<pBegin ) goto editpage_fail;
 
72718
72719 /* Add cells to the start of the page */
72720 if( iNew<iOld ){
72721 int nAdd = MIN(nNew,iOld-iNew);
72722 assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB );
@@ -74118,11 +74119,11 @@
74118 pBt = pPage->pBt;
74119 ovflPageSize = pBt->usableSize - 4;
74120 do{
74121 rc = btreeGetPage(pBt, ovflPgno, &pPage, 0);
74122 if( rc ) return rc;
74123 if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){
74124 rc = SQLITE_CORRUPT_BKPT;
74125 }else{
74126 if( iOffset+ovflPageSize<(u32)nTotal ){
74127 ovflPgno = get4byte(pPage->aData);
74128 }else{
@@ -94795,10 +94796,15 @@
94795 rc = SQLITE_NOMEM_BKPT;
94796 }else if( rc==SQLITE_IOERR_CORRUPTFS ){
94797 rc = SQLITE_CORRUPT_BKPT;
94798 }
94799 assert( rc );
 
 
 
 
 
94800 if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){
94801 sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
94802 }
94803 p->rc = rc;
94804 sqlite3SystemError(db, rc);
@@ -105161,10 +105167,11 @@
105161
105162 /***********************************************************************
105163 ** Test-only SQL functions that are only usable if enabled
105164 ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS
105165 */
 
105166 case INLINEFUNC_expr_compare: {
105167 /* Compare two expressions using sqlite3ExprCompare() */
105168 assert( nFarg==2 );
105169 sqlite3VdbeAddOp2(v, OP_Integer,
105170 sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1),
@@ -105194,11 +105201,10 @@
105194 sqlite3VdbeAddOp2(v, OP_Null, 0, target);
105195 }
105196 break;
105197 }
105198
105199 #ifdef SQLITE_DEBUG
105200 case INLINEFUNC_affinity: {
105201 /* The AFFINITY() function evaluates to a string that describes
105202 ** the type affinity of the argument. This is used for testing of
105203 ** the SQLite type logic.
105204 */
@@ -105208,11 +105214,11 @@
105208 aff = sqlite3ExprAffinity(pFarg->a[0].pExpr);
105209 sqlite3VdbeLoadString(v, target,
105210 (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]);
105211 break;
105212 }
105213 #endif
105214 }
105215 return target;
105216 }
105217
105218
@@ -108172,12 +108178,11 @@
108172 bQuote = sqlite3Isquote(pNew->z[0]);
108173 sqlite3NestedParse(pParse,
108174 "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET "
108175 "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
108176 "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' "
108177 " AND (type != 'index' OR tbl_name = %Q)"
108178 " AND sql NOT LIKE 'create virtual%%'",
108179 zDb,
108180 zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1,
108181 pTab->zName
108182 );
108183
@@ -109023,11 +109028,11 @@
109023 rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc);
109024 if( rc==SQLITE_OK ){
109025 sqlite3WalkSelect(&sWalker, pSelect);
109026 }
109027 if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
109028 }else if( ALWAYS(IsOrdinaryTable(sParse.pNewTable)) ){
109029 /* A regular table */
109030 int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName);
109031 FKey *pFKey;
109032 sCtx.pTab = sParse.pNewTable;
109033 if( bFKOnly==0 ){
@@ -120209,13 +120214,13 @@
120209 }
120210
120211 /*
120212 ** Implementation of the changes() SQL function.
120213 **
120214 ** IMP: R-62073-11209 The changes() SQL function is a wrapper
120215 ** around the sqlite3_changes64() C/C++ function and hence follows the same
120216 ** rules for counting changes.
120217 */
120218 static void changes(
120219 sqlite3_context *context,
120220 int NotUsed,
120221 sqlite3_value **NotUsed2
@@ -120234,12 +120239,12 @@
120234 int NotUsed,
120235 sqlite3_value **NotUsed2
120236 ){
120237 sqlite3 *db = sqlite3_context_db_handle(context);
120238 UNUSED_PARAMETER2(NotUsed, NotUsed2);
120239 /* IMP: R-52756-41993 This function was a wrapper around the
120240 ** sqlite3_total_changes() C/C++ interface. */
120241 sqlite3_result_int64(context, sqlite3_total_changes64(db));
120242 }
120243
120244 /*
120245 ** A structure defining how to do GLOB-style comparisons.
@@ -121761,16 +121766,16 @@
121761 **
121762 ** For peak efficiency, put the most frequently used function last.
121763 */
121764 static FuncDef aBuiltinFunc[] = {
121765 /***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/
 
121766 TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0),
121767 TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0),
121768 TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0),
121769 #ifdef SQLITE_DEBUG
121770 TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0),
121771 #endif
121772 /***** Regular functions *****/
121773 #ifdef SQLITE_SOUNDEX
121774 FUNCTION(soundex, 1, 0, 0, soundexFunc ),
121775 #endif
121776 #ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -128264,17 +128269,18 @@
128264 #define PragTyp_SECURE_DELETE 33
128265 #define PragTyp_SHRINK_MEMORY 34
128266 #define PragTyp_SOFT_HEAP_LIMIT 35
128267 #define PragTyp_SYNCHRONOUS 36
128268 #define PragTyp_TABLE_INFO 37
128269 #define PragTyp_TEMP_STORE 38
128270 #define PragTyp_TEMP_STORE_DIRECTORY 39
128271 #define PragTyp_THREADS 40
128272 #define PragTyp_WAL_AUTOCHECKPOINT 41
128273 #define PragTyp_WAL_CHECKPOINT 42
128274 #define PragTyp_LOCK_STATUS 43
128275 #define PragTyp_STATS 44
 
128276
128277 /* Property flags associated with various pragma. */
128278 #define PragFlg_NeedSchema 0x01 /* Force schema load before running */
128279 #define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */
128280 #define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */
@@ -128303,49 +128309,55 @@
128303 /* 11 */ "notnull",
128304 /* 12 */ "dflt_value",
128305 /* 13 */ "pk",
128306 /* 14 */ "hidden",
128307 /* table_info reuses 8 */
128308 /* 15 */ "seqno", /* Used by: index_xinfo */
128309 /* 16 */ "cid",
128310 /* 17 */ "name",
128311 /* 18 */ "desc",
128312 /* 19 */ "coll",
128313 /* 20 */ "key",
128314 /* 21 */ "name", /* Used by: function_list */
128315 /* 22 */ "builtin",
128316 /* 23 */ "type",
128317 /* 24 */ "enc",
128318 /* 25 */ "narg",
128319 /* 26 */ "flags",
128320 /* 27 */ "tbl", /* Used by: stats */
128321 /* 28 */ "idx",
128322 /* 29 */ "wdth",
128323 /* 30 */ "hght",
128324 /* 31 */ "flgs",
128325 /* 32 */ "seq", /* Used by: index_list */
128326 /* 33 */ "name",
128327 /* 34 */ "unique",
128328 /* 35 */ "origin",
128329 /* 36 */ "partial",
128330 /* 37 */ "table", /* Used by: foreign_key_check */
128331 /* 38 */ "rowid",
128332 /* 39 */ "parent",
128333 /* 40 */ "fkid",
128334 /* index_info reuses 15 */
128335 /* 41 */ "seq", /* Used by: database_list */
128336 /* 42 */ "name",
128337 /* 43 */ "file",
128338 /* 44 */ "busy", /* Used by: wal_checkpoint */
128339 /* 45 */ "log",
128340 /* 46 */ "checkpointed",
128341 /* collation_list reuses 32 */
128342 /* 47 */ "database", /* Used by: lock_status */
128343 /* 48 */ "status",
128344 /* 49 */ "cache_size", /* Used by: default_cache_size */
 
 
 
 
 
 
128345 /* module_list pragma_list reuses 9 */
128346 /* 50 */ "timeout", /* Used by: busy_timeout */
128347 };
128348
128349 /* Definitions of all built-in pragmas */
128350 typedef struct PragmaName {
128351 const char *const zName; /* Name of pragma */
@@ -128392,11 +128404,11 @@
128392 #endif
128393 #endif
128394 {/* zName: */ "busy_timeout",
128395 /* ePragTyp: */ PragTyp_BUSY_TIMEOUT,
128396 /* ePragFlg: */ PragFlg_Result0,
128397 /* ColNames: */ 50, 1,
128398 /* iArg: */ 0 },
128399 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128400 {/* zName: */ "cache_size",
128401 /* ePragTyp: */ PragTyp_CACHE_SIZE,
128402 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
@@ -128431,11 +128443,11 @@
128431 #endif
128432 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128433 {/* zName: */ "collation_list",
128434 /* ePragTyp: */ PragTyp_COLLATION_LIST,
128435 /* ePragFlg: */ PragFlg_Result0,
128436 /* ColNames: */ 32, 2,
128437 /* iArg: */ 0 },
128438 #endif
128439 #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
128440 {/* zName: */ "compile_options",
128441 /* ePragTyp: */ PragTyp_COMPILE_OPTIONS,
@@ -128466,18 +128478,18 @@
128466 #endif
128467 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128468 {/* zName: */ "database_list",
128469 /* ePragTyp: */ PragTyp_DATABASE_LIST,
128470 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
128471 /* ColNames: */ 41, 3,
128472 /* iArg: */ 0 },
128473 #endif
128474 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
128475 {/* zName: */ "default_cache_size",
128476 /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE,
128477 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
128478 /* ColNames: */ 49, 1,
128479 /* iArg: */ 0 },
128480 #endif
128481 #if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128482 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128483 {/* zName: */ "defer_foreign_keys",
@@ -128503,11 +128515,11 @@
128503 #endif
128504 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128505 {/* zName: */ "foreign_key_check",
128506 /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK,
128507 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt,
128508 /* ColNames: */ 37, 4,
128509 /* iArg: */ 0 },
128510 #endif
128511 #if !defined(SQLITE_OMIT_FOREIGN_KEY)
128512 {/* zName: */ "foreign_key_list",
128513 /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST,
@@ -128546,11 +128558,11 @@
128546 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128547 #if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
128548 {/* zName: */ "function_list",
128549 /* ePragTyp: */ PragTyp_FUNCTION_LIST,
128550 /* ePragFlg: */ PragFlg_Result0,
128551 /* ColNames: */ 21, 6,
128552 /* iArg: */ 0 },
128553 #endif
128554 #endif
128555 {/* zName: */ "hard_heap_limit",
128556 /* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT,
@@ -128575,21 +128587,21 @@
128575 #endif
128576 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128577 {/* zName: */ "index_info",
128578 /* ePragTyp: */ PragTyp_INDEX_INFO,
128579 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128580 /* ColNames: */ 15, 3,
128581 /* iArg: */ 0 },
128582 {/* zName: */ "index_list",
128583 /* ePragTyp: */ PragTyp_INDEX_LIST,
128584 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128585 /* ColNames: */ 32, 5,
128586 /* iArg: */ 0 },
128587 {/* zName: */ "index_xinfo",
128588 /* ePragTyp: */ PragTyp_INDEX_INFO,
128589 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128590 /* ColNames: */ 15, 6,
128591 /* iArg: */ 1 },
128592 #endif
128593 #if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
128594 {/* zName: */ "integrity_check",
128595 /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
@@ -128625,11 +128637,11 @@
128625 #endif
128626 #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
128627 {/* zName: */ "lock_status",
128628 /* ePragTyp: */ PragTyp_LOCK_STATUS,
128629 /* ePragFlg: */ PragFlg_Result0,
128630 /* ColNames: */ 47, 2,
128631 /* iArg: */ 0 },
128632 #endif
128633 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128634 {/* zName: */ "locking_mode",
128635 /* ePragTyp: */ PragTyp_LOCKING_MODE,
@@ -128764,11 +128776,11 @@
128764 #endif
128765 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG)
128766 {/* zName: */ "stats",
128767 /* ePragTyp: */ PragTyp_STATS,
128768 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
128769 /* ColNames: */ 27, 5,
128770 /* iArg: */ 0 },
128771 #endif
128772 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128773 {/* zName: */ "synchronous",
128774 /* ePragTyp: */ PragTyp_SYNCHRONOUS,
@@ -128780,10 +128792,15 @@
128780 {/* zName: */ "table_info",
128781 /* ePragTyp: */ PragTyp_TABLE_INFO,
128782 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128783 /* ColNames: */ 8, 6,
128784 /* iArg: */ 0 },
 
 
 
 
 
128785 {/* zName: */ "table_xinfo",
128786 /* ePragTyp: */ PragTyp_TABLE_INFO,
128787 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128788 /* ColNames: */ 8, 7,
128789 /* iArg: */ 1 },
@@ -128855,11 +128872,11 @@
128855 /* ColNames: */ 0, 0,
128856 /* iArg: */ 0 },
128857 {/* zName: */ "wal_checkpoint",
128858 /* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
128859 /* ePragFlg: */ PragFlg_NeedSchema,
128860 /* ColNames: */ 44, 3,
128861 /* iArg: */ 0 },
128862 #endif
128863 #if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128864 {/* zName: */ "writable_schema",
128865 /* ePragTyp: */ PragTyp_FLAG,
@@ -128866,11 +128883,11 @@
128866 /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
128867 /* ColNames: */ 0, 0,
128868 /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError },
128869 #endif
128870 };
128871 /* Number of pragmas: 67 on by default, 77 total. */
128872
128873 /************** End of pragma.h **********************************************/
128874 /************** Continuing where we left off in pragma.c *********************/
128875
128876 /*
@@ -130035,10 +130052,58 @@
130035 }
130036 }
130037 }
130038 break;
130039
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130040 #ifdef SQLITE_DEBUG
130041 case PragTyp_STATS: {
130042 Index *pIdx;
130043 HashElem *i;
130044 pParse->nMem = 5;
@@ -130544,11 +130609,13 @@
130544 }else{
130545 integrityCheckResultRow(v);
130546 }
130547 sqlite3VdbeJumpHere(v, jmp2);
130548 }
130549 if( pTab->tabFlags & TF_Strict ){
 
 
130550 jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0,
130551 sqlite3StdTypeMap[pCol->eCType-1]);
130552 VdbeCoverage(v);
130553 zErr = sqlite3MPrintf(db, "non-%s value in %s.%s",
130554 sqlite3StdType[pCol->eCType-1],
@@ -132836,10 +132903,13 @@
132836
132837 pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
132838 pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
132839
132840 pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2);
 
 
 
132841 if( pEq && isOuterJoin ){
132842 ExprSetProperty(pEq, EP_FromJoin);
132843 assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
132844 ExprSetVVAProperty(pEq, EP_NoReduce);
132845 pEq->iRightJoinTable = pE2->iTable;
@@ -148365,11 +148435,11 @@
148365 ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975
148366 ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a
148367 */
148368 if( pLeft->op!=TK_COLUMN
148369 || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
148370 || IsVirtual(pLeft->y.pTab) /* Value might be numeric */
148371 ){
148372 int isNum;
148373 double rDummy;
148374 isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8);
148375 if( isNum<=0 ){
@@ -156490,10 +156560,13 @@
156490 );
156491 SELECTTRACE(1,pParse,pSub,
156492 ("New window-function subquery in FROM clause of (%u/%p)\n",
156493 p->selId, p));
156494 p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
 
 
 
156495 if( p->pSrc ){
156496 Table *pTab2;
156497 p->pSrc->a[0].pSelect = pSub;
156498 sqlite3SrcListAssignCursors(pParse, p->pSrc);
156499 pSub->selFlags |= SF_Expanded|SF_OrderByReqd;
@@ -192908,11 +192981,15 @@
192908 #else
192909 /* #include "sqlite3.h" */
192910 #endif
192911 SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
192912
192913 #ifndef SQLITE_AMALGAMATION
 
 
 
 
192914 #include "sqlite3rtree.h"
192915 typedef sqlite3_int64 i64;
192916 typedef sqlite3_uint64 u64;
192917 typedef unsigned char u8;
192918 typedef unsigned short u16;
@@ -192921,11 +192998,21 @@
192921 # define NDEBUG 1
192922 #endif
192923 #if defined(NDEBUG) && defined(SQLITE_DEBUG)
192924 # undef NDEBUG
192925 #endif
 
 
 
 
 
 
 
 
 
192926 #endif
 
192927
192928 /* #include <string.h> */
192929 /* #include <stdio.h> */
192930 /* #include <assert.h> */
192931 /* #include <stdlib.h> */
@@ -192979,11 +193066,13 @@
192979 u8 nDim2; /* Twice the number of dimensions */
192980 u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
192981 u8 nBytesPerCell; /* Bytes consumed per cell */
192982 u8 inWrTrans; /* True if inside write transaction */
192983 u8 nAux; /* # of auxiliary columns in %_rowid */
 
192984 u8 nAuxNotNull; /* Number of initial not-null aux columns */
 
192985 #ifdef SQLITE_DEBUG
192986 u8 bCorrupt; /* Shadow table corruption detected */
192987 #endif
192988 int iDepth; /* Current depth of the r-tree structure */
192989 char *zDb; /* Name of database containing r-tree table */
@@ -193510,22 +193599,10 @@
193510 pRtree->pNodeBlob = 0;
193511 sqlite3_blob_close(pBlob);
193512 }
193513 }
193514
193515 /*
193516 ** Check to see if pNode is the same as pParent or any of the parents
193517 ** of pParent.
193518 */
193519 static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){
193520 do{
193521 if( pNode==pParent ) return 1;
193522 pParent = pParent->pParent;
193523 }while( pParent );
193524 return 0;
193525 }
193526
193527 /*
193528 ** Obtain a reference to an r-tree node.
193529 */
193530 static int nodeAcquire(
193531 Rtree *pRtree, /* R-tree structure */
@@ -193538,18 +193615,11 @@
193538
193539 /* Check if the requested node is already in the hash table. If so,
193540 ** increase its reference count and return it.
193541 */
193542 if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){
193543 if( pParent && !pNode->pParent ){
193544 if( nodeInParentChain(pNode, pParent) ){
193545 RTREE_IS_CORRUPT(pRtree);
193546 return SQLITE_CORRUPT_VTAB;
193547 }
193548 pParent->nRef++;
193549 pNode->pParent = pParent;
193550 }else if( pParent && pNode->pParent && pParent!=pNode->pParent ){
193551 RTREE_IS_CORRUPT(pRtree);
193552 return SQLITE_CORRUPT_VTAB;
193553 }
193554 pNode->nRef++;
193555 *ppNode = pNode;
@@ -193603,11 +193673,11 @@
193603 ** of the r-tree structure. A height of zero means all data is stored on
193604 ** the root node. A height of one means the children of the root node
193605 ** are the leaves, and so on. If the depth as specified on the root node
193606 ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt.
193607 */
193608 if( pNode && rc==SQLITE_OK && iNode==1 ){
193609 pRtree->iDepth = readInt16(pNode->zData);
193610 if( pRtree->iDepth>RTREE_MAX_DEPTH ){
193611 rc = SQLITE_CORRUPT_VTAB;
193612 RTREE_IS_CORRUPT(pRtree);
193613 }
@@ -194209,15 +194279,16 @@
194209 ** Return the index of the cell containing a pointer to node pNode
194210 ** in its parent. If pNode is the root node, return -1.
194211 */
194212 static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){
194213 RtreeNode *pParent = pNode->pParent;
194214 if( pParent ){
194215 return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex);
 
 
 
194216 }
194217 *piIndex = -1;
194218 return SQLITE_OK;
194219 }
194220
194221 /*
194222 ** Compare two search points. Return negative, zero, or positive if the first
194223 ** is less than, equal to, or greater than the second.
@@ -194336,11 +194407,12 @@
194336 if( pCur->bPoint ){
194337 int ii;
194338 pNew = rtreeEnqueue(pCur, rScore, iLevel);
194339 if( pNew==0 ) return 0;
194340 ii = (int)(pNew - pCur->aPoint) + 1;
194341 if( ii<RTREE_CACHE_SZ ){
 
194342 assert( pCur->aNode[ii]==0 );
194343 pCur->aNode[ii] = pCur->aNode[0];
194344 }else{
194345 nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]);
194346 }
@@ -194397,11 +194469,11 @@
194397 p->aNode[i] = 0;
194398 }
194399 if( p->bPoint ){
194400 p->anQueue[p->sPoint.iLevel]--;
194401 p->bPoint = 0;
194402 }else if( p->nPoint ){
194403 p->anQueue[p->aPoint[0].iLevel]--;
194404 n = --p->nPoint;
194405 p->aPoint[0] = p->aPoint[n];
194406 if( n<RTREE_CACHE_SZ-1 ){
194407 p->aNode[1] = p->aNode[n+1];
@@ -194538,11 +194610,11 @@
194538 static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
194539 RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
194540 RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
194541 int rc = SQLITE_OK;
194542 RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194543 if( rc==SQLITE_OK && p ){
194544 *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
194545 }
194546 return rc;
194547 }
194548
@@ -194556,11 +194628,11 @@
194556 RtreeCoord c;
194557 int rc = SQLITE_OK;
194558 RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194559
194560 if( rc ) return rc;
194561 if( p==0 ) return SQLITE_OK;
194562 if( i==0 ){
194563 sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
194564 }else if( i<=pRtree->nDim2 ){
194565 nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
194566 #ifndef SQLITE_RTREE_INT_ONLY
@@ -194755,12 +194827,15 @@
194755 }
194756 }
194757 }
194758 if( rc==SQLITE_OK ){
194759 RtreeSearchPoint *pNew;
 
194760 pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
194761 if( pNew==0 ) return SQLITE_NOMEM;
 
 
194762 pNew->id = 1;
194763 pNew->iCell = 0;
194764 pNew->eWithin = PARTLY_WITHIN;
194765 assert( pCsr->bPoint==1 );
194766 pCsr->aNode[0] = pRoot;
@@ -194833,11 +194908,11 @@
194833 assert( pIdxInfo->idxStr==0 );
194834 for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
194835 struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
194836
194837 if( bMatch==0 && p->usable
194838 && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
194839 ){
194840 /* We have an equality constraint on the rowid. Use strategy 1. */
194841 int jj;
194842 for(jj=0; jj<ii; jj++){
194843 pIdxInfo->aConstraintUsage[jj].argvIndex = 0;
@@ -195086,16 +195161,23 @@
195086 RtreeNode *pNode, /* Adjust ancestry of this node. */
195087 RtreeCell *pCell /* This cell was just inserted */
195088 ){
195089 RtreeNode *p = pNode;
195090 int cnt = 0;
 
195091 while( p->pParent ){
195092 RtreeNode *pParent = p->pParent;
195093 RtreeCell cell;
195094 int iCell;
195095
195096 if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){
 
 
 
 
 
 
195097 RTREE_IS_CORRUPT(pRtree);
195098 return SQLITE_CORRUPT_VTAB;
195099 }
195100
195101 nodeGetCell(pRtree, pParent, iCell, &cell);
@@ -195475,15 +195557,16 @@
195475 }
195476 }else{
195477 RtreeNode *pParent = pLeft->pParent;
195478 int iCell;
195479 rc = nodeParentIndex(pRtree, pLeft, &iCell);
195480 if( rc==SQLITE_OK ){
195481 nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell);
195482 rc = AdjustTree(pRtree, pParent, &leftbbox);
 
195483 }
195484 if( rc!=SQLITE_OK ){
195485 goto splitnode_out;
195486 }
195487 }
195488 if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){
195489 goto splitnode_out;
@@ -195554,11 +195637,11 @@
195554 ** want to do this as it leads to a memory leak when trying to delete
195555 ** the referenced counted node structures.
195556 */
195557 iNode = sqlite3_column_int64(pRtree->pReadParent, 0);
195558 for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent);
195559 if( !pTest ){
195560 rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent);
195561 }
195562 }
195563 rc = sqlite3_reset(pRtree->pReadParent);
195564 if( rc==SQLITE_OK ) rc = rc2;
@@ -195585,10 +195668,11 @@
195585 rc = nodeParentIndex(pRtree, pNode, &iCell);
195586 if( rc==SQLITE_OK ){
195587 pParent = pNode->pParent;
195588 pNode->pParent = 0;
195589 rc = deleteCell(pRtree, pParent, iCell, iHeight+1);
 
195590 }
195591 rc2 = nodeRelease(pRtree, pParent);
195592 if( rc==SQLITE_OK ){
195593 rc = rc2;
195594 }
@@ -195807,11 +195891,11 @@
195807 pRtree->iReinsertHeight = iHeight;
195808 rc = Reinsert(pRtree, pNode, pCell, iHeight);
195809 }
195810 }else{
195811 rc = AdjustTree(pRtree, pNode, pCell);
195812 if( rc==SQLITE_OK ){
195813 if( iHeight==0 ){
195814 rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode);
195815 }else{
195816 rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode);
195817 }
@@ -195913,11 +195997,11 @@
195913 */
195914 if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
195915 int rc2;
195916 RtreeNode *pChild = 0;
195917 i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
195918 rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
195919 if( rc==SQLITE_OK ){
195920 rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
195921 }
195922 rc2 = nodeRelease(pRtree, pChild);
195923 if( rc==SQLITE_OK ) rc = rc2;
@@ -196248,11 +196332,11 @@
196248 static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
196249 const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'";
196250 char *zSql;
196251 sqlite3_stmt *p;
196252 int rc;
196253 i64 nRow = 0;
196254
196255 rc = sqlite3_table_column_metadata(
196256 db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
196257 );
196258 if( rc!=SQLITE_OK ){
@@ -196265,24 +196349,14 @@
196265 }else{
196266 rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0);
196267 if( rc==SQLITE_OK ){
196268 if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0);
196269 rc = sqlite3_finalize(p);
196270 }else if( rc!=SQLITE_NOMEM ){
196271 rc = SQLITE_OK;
196272 }
196273
196274 if( rc==SQLITE_OK ){
196275 if( nRow==0 ){
196276 pRtree->nRowEst = RTREE_DEFAULT_ROWEST;
196277 }else{
196278 pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
196279 }
196280 }
196281 sqlite3_free(zSql);
196282 }
196283
196284 return rc;
196285 }
196286
196287
196288 /*
@@ -196428,13 +196502,16 @@
196428 int ii;
196429 char *zSql;
196430 sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
196431 for(ii=0; ii<pRtree->nAux; ii++){
196432 if( ii ) sqlite3_str_append(p, ",", 1);
 
196433 if( ii<pRtree->nAuxNotNull ){
196434 sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii);
196435 }else{
 
 
196436 sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
196437 }
196438 }
196439 sqlite3_str_appendf(p, " WHERE rowid=?1");
196440 zSql = sqlite3_str_finish(p);
@@ -197109,12 +197186,14 @@
197109 if( check.rc==SQLITE_OK ){
197110 pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
197111 if( pStmt ){
197112 nAux = sqlite3_column_count(pStmt) - 2;
197113 sqlite3_finalize(pStmt);
 
 
 
197114 }
197115 check.rc = SQLITE_OK;
197116 }
197117
197118 /* Find number of dimensions in the rtree table. */
197119 pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
197120 if( pStmt ){
@@ -199185,11 +199264,14 @@
199185 ){
199186 RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
199187
199188 /* Allocate and populate the context object. */
199189 pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
199190 if( !pGeomCtx ) return SQLITE_NOMEM;
 
 
 
199191 pGeomCtx->xGeom = 0;
199192 pGeomCtx->xQueryFunc = xQueryFunc;
199193 pGeomCtx->xDestructor = xDestructor;
199194 pGeomCtx->pContext = pContext;
199195 return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
@@ -222468,10 +222550,46 @@
222468 if( p->pStruct!=(Fts5Structure*)pStruct ){
222469 return SQLITE_ABORT;
222470 }
222471 return SQLITE_OK;
222472 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222473
222474 /*
222475 ** Deserialize and return the structure record currently stored in serialized
222476 ** form within buffer pData/nData.
222477 **
@@ -222570,13 +222688,15 @@
222570 *ppOut = pRet;
222571 return rc;
222572 }
222573
222574 /*
222575 **
 
222576 */
222577 static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
 
222578 if( *pRc==SQLITE_OK ){
222579 Fts5Structure *pStruct = *ppStruct;
222580 int nLevel = pStruct->nLevel;
222581 sqlite3_int64 nByte = (
222582 sizeof(Fts5Structure) + /* Main structure */
@@ -231175,11 +231295,11 @@
231175 int nArg, /* Number of args */
231176 sqlite3_value **apUnused /* Function arguments */
231177 ){
231178 assert( nArg==0 );
231179 UNUSED_PARAM2(nArg, apUnused);
231180 sqlite3_result_text(pCtx, "fts5: 2021-09-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1", -1, SQLITE_TRANSIENT);
231181 }
231182
231183 /*
231184 ** Return true if zName is the extension on one of the shadow tables used
231185 ** by this module.
231186
--- 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 **
@@ -19842,11 +19842,11 @@
19842 #ifdef SQLITE_TEST
19843 SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*);
19844 #endif
19845
19846 #ifdef SQLITE_OMIT_VIRTUALTABLE
19847 # define sqlite3VtabClear(D,T)
19848 # define sqlite3VtabSync(X,Y) SQLITE_OK
19849 # define sqlite3VtabRollback(X)
19850 # define sqlite3VtabCommit(X)
19851 # define sqlite3VtabInSync(db) 0
19852 # define sqlite3VtabLock(X)
@@ -48856,11 +48856,11 @@
48856 MemStore *p = 0;
48857 int szName;
48858 if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
48859 return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFd, flags, pOutFlags);
48860 }
48861 memset(pFile, 0, sizeof(*pFile));
48862 szName = sqlite3Strlen30(zName);
48863 if( szName>1 && zName[0]=='/' ){
48864 int i;
48865 #ifndef SQLITE_MUTEX_OMIT
48866 sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
@@ -53160,12 +53160,12 @@
53160
53161 u16 nExtra; /* Add this many bytes to each in-memory page */
53162 i16 nReserve; /* Number of unused bytes at end of each page */
53163 u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
53164 u32 sectorSize; /* Assumed sector size during rollback */
 
53165 Pgno mxPgno; /* Maximum allowed size of the database */
53166 i64 pageSize; /* Number of bytes in a page */
53167 i64 journalSizeLimit; /* Size limit for persistent journal files */
53168 char *zFilename; /* Name of the database file */
53169 char *zJournal; /* Name of the journal file */
53170 int (*xBusyHandler)(void*); /* Function to call when busy */
53171 void *pBusyHandlerArg; /* Context argument for xBusyHandler */
@@ -59218,12 +59218,12 @@
59218 /*
59219 ** Return the approximate number of bytes of memory currently
59220 ** used by the pager and its associated cache.
59221 */
59222 SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){
59223 int perPageSize = pPager->pageSize + pPager->nExtra
59224 + (int)(sizeof(PgHdr) + 5*sizeof(void*));
59225 return perPageSize*sqlite3PcachePagecount(pPager->pPCache)
59226 + sqlite3MallocSize(pPager)
59227 + pPager->pageSize;
59228 }
59229
@@ -59413,18 +59413,18 @@
59413 for(ii=nNew; ii<pPager->nSavepoint; ii++){
59414 sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
59415 }
59416 pPager->nSavepoint = nNew;
59417
59418 /* Truncate the sub-journal so that it only includes the parts
59419 ** that are still in use. */
59420 if( op==SAVEPOINT_RELEASE ){
59421 PagerSavepoint *pRel = &pPager->aSavepoint[nNew];
59422 if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){
59423 /* Only truncate if it is an in-memory sub-journal. */
59424 if( sqlite3JournalIsInMemory(pPager->sjfd) ){
59425 i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec;
59426 rc = sqlite3OsTruncate(pPager->sjfd, sz);
59427 assert( rc==SQLITE_OK );
59428 }
59429 pPager->nSubRec = pRel->iSubRec;
59430 }
@@ -72713,10 +72713,11 @@
72713 nCell -= nTail;
72714 }
72715
72716 pData = &aData[get2byteNotZero(&aData[hdr+5])];
72717 if( pData<pBegin ) goto editpage_fail;
72718 if( NEVER(pData>pPg->aDataEnd) ) goto editpage_fail;
72719
72720 /* Add cells to the start of the page */
72721 if( iNew<iOld ){
72722 int nAdd = MIN(nNew,iOld-iNew);
72723 assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB );
@@ -74118,11 +74119,11 @@
74119 pBt = pPage->pBt;
74120 ovflPageSize = pBt->usableSize - 4;
74121 do{
74122 rc = btreeGetPage(pBt, ovflPgno, &pPage, 0);
74123 if( rc ) return rc;
74124 if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){
74125 rc = SQLITE_CORRUPT_BKPT;
74126 }else{
74127 if( iOffset+ovflPageSize<(u32)nTotal ){
74128 ovflPgno = get4byte(pPage->aData);
74129 }else{
@@ -94795,10 +94796,15 @@
94796 rc = SQLITE_NOMEM_BKPT;
94797 }else if( rc==SQLITE_IOERR_CORRUPTFS ){
94798 rc = SQLITE_CORRUPT_BKPT;
94799 }
94800 assert( rc );
94801 #ifdef SQLITE_DEBUG
94802 if( db->flags & SQLITE_VdbeTrace ){
94803 printf("ABORT-due-to-error. rc=%d\n", rc);
94804 }
94805 #endif
94806 if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){
94807 sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
94808 }
94809 p->rc = rc;
94810 sqlite3SystemError(db, rc);
@@ -105161,10 +105167,11 @@
105167
105168 /***********************************************************************
105169 ** Test-only SQL functions that are only usable if enabled
105170 ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS
105171 */
105172 #if !defined(SQLITE_UNTESTABLE)
105173 case INLINEFUNC_expr_compare: {
105174 /* Compare two expressions using sqlite3ExprCompare() */
105175 assert( nFarg==2 );
105176 sqlite3VdbeAddOp2(v, OP_Integer,
105177 sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1),
@@ -105194,11 +105201,10 @@
105201 sqlite3VdbeAddOp2(v, OP_Null, 0, target);
105202 }
105203 break;
105204 }
105205
 
105206 case INLINEFUNC_affinity: {
105207 /* The AFFINITY() function evaluates to a string that describes
105208 ** the type affinity of the argument. This is used for testing of
105209 ** the SQLite type logic.
105210 */
@@ -105208,11 +105214,11 @@
105214 aff = sqlite3ExprAffinity(pFarg->a[0].pExpr);
105215 sqlite3VdbeLoadString(v, target,
105216 (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]);
105217 break;
105218 }
105219 #endif /* !defined(SQLITE_UNTESTABLE) */
105220 }
105221 return target;
105222 }
105223
105224
@@ -108172,12 +108178,11 @@
108178 bQuote = sqlite3Isquote(pNew->z[0]);
108179 sqlite3NestedParse(pParse,
108180 "UPDATE \"%w\"." DFLT_SCHEMA_TABLE " SET "
108181 "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
108182 "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' "
108183 " AND (type != 'index' OR tbl_name = %Q)",
 
108184 zDb,
108185 zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1,
108186 pTab->zName
108187 );
108188
@@ -109023,11 +109028,11 @@
109028 rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc);
109029 if( rc==SQLITE_OK ){
109030 sqlite3WalkSelect(&sWalker, pSelect);
109031 }
109032 if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
109033 }else if( IsOrdinaryTable(sParse.pNewTable) ){
109034 /* A regular table */
109035 int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName);
109036 FKey *pFKey;
109037 sCtx.pTab = sParse.pNewTable;
109038 if( bFKOnly==0 ){
@@ -120209,13 +120214,13 @@
120214 }
120215
120216 /*
120217 ** Implementation of the changes() SQL function.
120218 **
120219 ** IMP: R-32760-32347 The changes() SQL function is a wrapper
120220 ** around the sqlite3_changes64() C/C++ function and hence follows the
120221 ** same rules for counting changes.
120222 */
120223 static void changes(
120224 sqlite3_context *context,
120225 int NotUsed,
120226 sqlite3_value **NotUsed2
@@ -120234,12 +120239,12 @@
120239 int NotUsed,
120240 sqlite3_value **NotUsed2
120241 ){
120242 sqlite3 *db = sqlite3_context_db_handle(context);
120243 UNUSED_PARAMETER2(NotUsed, NotUsed2);
120244 /* IMP: R-11217-42568 This function is a wrapper around the
120245 ** sqlite3_total_changes64() C/C++ interface. */
120246 sqlite3_result_int64(context, sqlite3_total_changes64(db));
120247 }
120248
120249 /*
120250 ** A structure defining how to do GLOB-style comparisons.
@@ -121761,16 +121766,16 @@
121766 **
121767 ** For peak efficiency, put the most frequently used function last.
121768 */
121769 static FuncDef aBuiltinFunc[] = {
121770 /***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/
121771 #if !defined(SQLITE_UNTESTABLE)
121772 TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0),
121773 TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0),
121774 TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0),
121775 TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0),
121776 #endif /* !defined(SQLITE_UNTESTABLE) */
 
121777 /***** Regular functions *****/
121778 #ifdef SQLITE_SOUNDEX
121779 FUNCTION(soundex, 1, 0, 0, soundexFunc ),
121780 #endif
121781 #ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -128264,17 +128269,18 @@
128269 #define PragTyp_SECURE_DELETE 33
128270 #define PragTyp_SHRINK_MEMORY 34
128271 #define PragTyp_SOFT_HEAP_LIMIT 35
128272 #define PragTyp_SYNCHRONOUS 36
128273 #define PragTyp_TABLE_INFO 37
128274 #define PragTyp_TABLE_LIST 38
128275 #define PragTyp_TEMP_STORE 39
128276 #define PragTyp_TEMP_STORE_DIRECTORY 40
128277 #define PragTyp_THREADS 41
128278 #define PragTyp_WAL_AUTOCHECKPOINT 42
128279 #define PragTyp_WAL_CHECKPOINT 43
128280 #define PragTyp_LOCK_STATUS 44
128281 #define PragTyp_STATS 45
128282
128283 /* Property flags associated with various pragma. */
128284 #define PragFlg_NeedSchema 0x01 /* Force schema load before running */
128285 #define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */
128286 #define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */
@@ -128303,49 +128309,55 @@
128309 /* 11 */ "notnull",
128310 /* 12 */ "dflt_value",
128311 /* 13 */ "pk",
128312 /* 14 */ "hidden",
128313 /* table_info reuses 8 */
128314 /* 15 */ "schema", /* Used by: table_list */
128315 /* 16 */ "name",
128316 /* 17 */ "type",
128317 /* 18 */ "ncol",
128318 /* 19 */ "wr",
128319 /* 20 */ "strict",
128320 /* 21 */ "seqno", /* Used by: index_xinfo */
128321 /* 22 */ "cid",
128322 /* 23 */ "name",
128323 /* 24 */ "desc",
128324 /* 25 */ "coll",
128325 /* 26 */ "key",
128326 /* 27 */ "name", /* Used by: function_list */
128327 /* 28 */ "builtin",
128328 /* 29 */ "type",
128329 /* 30 */ "enc",
128330 /* 31 */ "narg",
128331 /* 32 */ "flags",
128332 /* 33 */ "tbl", /* Used by: stats */
128333 /* 34 */ "idx",
128334 /* 35 */ "wdth",
128335 /* 36 */ "hght",
128336 /* 37 */ "flgs",
128337 /* 38 */ "seq", /* Used by: index_list */
128338 /* 39 */ "name",
128339 /* 40 */ "unique",
128340 /* 41 */ "origin",
128341 /* 42 */ "partial",
128342 /* 43 */ "table", /* Used by: foreign_key_check */
128343 /* 44 */ "rowid",
128344 /* 45 */ "parent",
128345 /* 46 */ "fkid",
128346 /* index_info reuses 21 */
128347 /* 47 */ "seq", /* Used by: database_list */
128348 /* 48 */ "name",
128349 /* 49 */ "file",
128350 /* 50 */ "busy", /* Used by: wal_checkpoint */
128351 /* 51 */ "log",
128352 /* 52 */ "checkpointed",
128353 /* collation_list reuses 38 */
128354 /* 53 */ "database", /* Used by: lock_status */
128355 /* 54 */ "status",
128356 /* 55 */ "cache_size", /* Used by: default_cache_size */
128357 /* module_list pragma_list reuses 9 */
128358 /* 56 */ "timeout", /* Used by: busy_timeout */
128359 };
128360
128361 /* Definitions of all built-in pragmas */
128362 typedef struct PragmaName {
128363 const char *const zName; /* Name of pragma */
@@ -128392,11 +128404,11 @@
128404 #endif
128405 #endif
128406 {/* zName: */ "busy_timeout",
128407 /* ePragTyp: */ PragTyp_BUSY_TIMEOUT,
128408 /* ePragFlg: */ PragFlg_Result0,
128409 /* ColNames: */ 56, 1,
128410 /* iArg: */ 0 },
128411 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128412 {/* zName: */ "cache_size",
128413 /* ePragTyp: */ PragTyp_CACHE_SIZE,
128414 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
@@ -128431,11 +128443,11 @@
128443 #endif
128444 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128445 {/* zName: */ "collation_list",
128446 /* ePragTyp: */ PragTyp_COLLATION_LIST,
128447 /* ePragFlg: */ PragFlg_Result0,
128448 /* ColNames: */ 38, 2,
128449 /* iArg: */ 0 },
128450 #endif
128451 #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
128452 {/* zName: */ "compile_options",
128453 /* ePragTyp: */ PragTyp_COMPILE_OPTIONS,
@@ -128466,18 +128478,18 @@
128478 #endif
128479 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128480 {/* zName: */ "database_list",
128481 /* ePragTyp: */ PragTyp_DATABASE_LIST,
128482 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
128483 /* ColNames: */ 47, 3,
128484 /* iArg: */ 0 },
128485 #endif
128486 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
128487 {/* zName: */ "default_cache_size",
128488 /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE,
128489 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
128490 /* ColNames: */ 55, 1,
128491 /* iArg: */ 0 },
128492 #endif
128493 #if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128494 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128495 {/* zName: */ "defer_foreign_keys",
@@ -128503,11 +128515,11 @@
128515 #endif
128516 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
128517 {/* zName: */ "foreign_key_check",
128518 /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK,
128519 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt,
128520 /* ColNames: */ 43, 4,
128521 /* iArg: */ 0 },
128522 #endif
128523 #if !defined(SQLITE_OMIT_FOREIGN_KEY)
128524 {/* zName: */ "foreign_key_list",
128525 /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST,
@@ -128546,11 +128558,11 @@
128558 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128559 #if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
128560 {/* zName: */ "function_list",
128561 /* ePragTyp: */ PragTyp_FUNCTION_LIST,
128562 /* ePragFlg: */ PragFlg_Result0,
128563 /* ColNames: */ 27, 6,
128564 /* iArg: */ 0 },
128565 #endif
128566 #endif
128567 {/* zName: */ "hard_heap_limit",
128568 /* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT,
@@ -128575,21 +128587,21 @@
128587 #endif
128588 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
128589 {/* zName: */ "index_info",
128590 /* ePragTyp: */ PragTyp_INDEX_INFO,
128591 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128592 /* ColNames: */ 21, 3,
128593 /* iArg: */ 0 },
128594 {/* zName: */ "index_list",
128595 /* ePragTyp: */ PragTyp_INDEX_LIST,
128596 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128597 /* ColNames: */ 38, 5,
128598 /* iArg: */ 0 },
128599 {/* zName: */ "index_xinfo",
128600 /* ePragTyp: */ PragTyp_INDEX_INFO,
128601 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128602 /* ColNames: */ 21, 6,
128603 /* iArg: */ 1 },
128604 #endif
128605 #if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
128606 {/* zName: */ "integrity_check",
128607 /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
@@ -128625,11 +128637,11 @@
128637 #endif
128638 #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
128639 {/* zName: */ "lock_status",
128640 /* ePragTyp: */ PragTyp_LOCK_STATUS,
128641 /* ePragFlg: */ PragFlg_Result0,
128642 /* ColNames: */ 53, 2,
128643 /* iArg: */ 0 },
128644 #endif
128645 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128646 {/* zName: */ "locking_mode",
128647 /* ePragTyp: */ PragTyp_LOCKING_MODE,
@@ -128764,11 +128776,11 @@
128776 #endif
128777 #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG)
128778 {/* zName: */ "stats",
128779 /* ePragTyp: */ PragTyp_STATS,
128780 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
128781 /* ColNames: */ 33, 5,
128782 /* iArg: */ 0 },
128783 #endif
128784 #if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
128785 {/* zName: */ "synchronous",
128786 /* ePragTyp: */ PragTyp_SYNCHRONOUS,
@@ -128780,10 +128792,15 @@
128792 {/* zName: */ "table_info",
128793 /* ePragTyp: */ PragTyp_TABLE_INFO,
128794 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128795 /* ColNames: */ 8, 6,
128796 /* iArg: */ 0 },
128797 {/* zName: */ "table_list",
128798 /* ePragTyp: */ PragTyp_TABLE_LIST,
128799 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1,
128800 /* ColNames: */ 15, 6,
128801 /* iArg: */ 1 },
128802 {/* zName: */ "table_xinfo",
128803 /* ePragTyp: */ PragTyp_TABLE_INFO,
128804 /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
128805 /* ColNames: */ 8, 7,
128806 /* iArg: */ 1 },
@@ -128855,11 +128872,11 @@
128872 /* ColNames: */ 0, 0,
128873 /* iArg: */ 0 },
128874 {/* zName: */ "wal_checkpoint",
128875 /* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
128876 /* ePragFlg: */ PragFlg_NeedSchema,
128877 /* ColNames: */ 50, 3,
128878 /* iArg: */ 0 },
128879 #endif
128880 #if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
128881 {/* zName: */ "writable_schema",
128882 /* ePragTyp: */ PragTyp_FLAG,
@@ -128866,11 +128883,11 @@
128883 /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
128884 /* ColNames: */ 0, 0,
128885 /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError },
128886 #endif
128887 };
128888 /* Number of pragmas: 68 on by default, 78 total. */
128889
128890 /************** End of pragma.h **********************************************/
128891 /************** Continuing where we left off in pragma.c *********************/
128892
128893 /*
@@ -130035,10 +130052,58 @@
130052 }
130053 }
130054 }
130055 break;
130056
130057 /*
130058 ** PRAGMA table_list
130059 **
130060 ** Return a single row for each table, virtual table, or view in the
130061 ** entire schema.
130062 **
130063 ** schema: Name of attached database hold this table
130064 ** name: Name of the table itself
130065 ** type: "table", "view", "virtual", "shadow"
130066 ** ncol: Number of columns
130067 ** wr: True for a WITHOUT ROWID table
130068 ** strict: True for a STRICT table
130069 */
130070 case PragTyp_TABLE_LIST: {
130071 int ii;
130072 pParse->nMem = 6;
130073 sqlite3CodeVerifyNamedSchema(pParse, zDb);
130074 for(ii=0; ii<db->nDb; ii++){
130075 HashElem *k;
130076 Hash *pHash;
130077 if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue;
130078 pHash = &db->aDb[ii].pSchema->tblHash;
130079 for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){
130080 Table *pTab = sqliteHashData(k);
130081 const char *zType;
130082 if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue;
130083 if( IsView(pTab) ){
130084 zType = "view";
130085 }else if( IsVirtual(pTab) ){
130086 zType = "virtual";
130087 }else if( pTab->tabFlags & TF_Shadow ){
130088 zType = "shadow";
130089 }else{
130090 zType = "table";
130091 }
130092 sqlite3VdbeMultiLoad(v, 1, "sssiii",
130093 db->aDb[ii].zDbSName,
130094 pTab->zName,
130095 zType,
130096 pTab->nCol,
130097 (pTab->tabFlags & TF_WithoutRowid)!=0,
130098 (pTab->tabFlags & TF_Strict)!=0
130099 );
130100 }
130101 }
130102 }
130103 break;
130104
130105 #ifdef SQLITE_DEBUG
130106 case PragTyp_STATS: {
130107 Index *pIdx;
130108 HashElem *i;
130109 pParse->nMem = 5;
@@ -130544,11 +130609,13 @@
130609 }else{
130610 integrityCheckResultRow(v);
130611 }
130612 sqlite3VdbeJumpHere(v, jmp2);
130613 }
130614 if( (pTab->tabFlags & TF_Strict)!=0
130615 && pCol->eCType!=COLTYPE_ANY
130616 ){
130617 jmp2 = sqlite3VdbeAddOp3(v, OP_IsNullOrType, 3, 0,
130618 sqlite3StdTypeMap[pCol->eCType-1]);
130619 VdbeCoverage(v);
130620 zErr = sqlite3MPrintf(db, "non-%s value in %s.%s",
130621 sqlite3StdType[pCol->eCType-1],
@@ -132836,10 +132903,13 @@
132903
132904 pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
132905 pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
132906
132907 pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2);
132908 assert( pE2!=0 || pEq==0 ); /* Due to db->mallocFailed test
132909 ** in sqlite3DbMallocRawNN() called from
132910 ** sqlite3PExpr(). */
132911 if( pEq && isOuterJoin ){
132912 ExprSetProperty(pEq, EP_FromJoin);
132913 assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
132914 ExprSetVVAProperty(pEq, EP_NoReduce);
132915 pEq->iRightJoinTable = pE2->iTable;
@@ -148365,11 +148435,11 @@
148435 ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975
148436 ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a
148437 */
148438 if( pLeft->op!=TK_COLUMN
148439 || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
148440 || (pLeft->y.pTab && IsVirtual(pLeft->y.pTab)) /* Might be numeric */
148441 ){
148442 int isNum;
148443 double rDummy;
148444 isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8);
148445 if( isNum<=0 ){
@@ -156490,10 +156560,13 @@
156560 );
156561 SELECTTRACE(1,pParse,pSub,
156562 ("New window-function subquery in FROM clause of (%u/%p)\n",
156563 p->selId, p));
156564 p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
156565 assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside
156566 ** of sqlite3DbMallocRawNN() called from
156567 ** sqlite3SrcListAppend() */
156568 if( p->pSrc ){
156569 Table *pTab2;
156570 p->pSrc->a[0].pSelect = pSub;
156571 sqlite3SrcListAssignCursors(pParse, p->pSrc);
156572 pSub->selFlags |= SF_Expanded|SF_OrderByReqd;
@@ -192908,11 +192981,15 @@
192981 #else
192982 /* #include "sqlite3.h" */
192983 #endif
192984 SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
192985
192986 /*
192987 ** If building separately, we will need some setup that is normally
192988 ** found in sqliteInt.h
192989 */
192990 #if !defined(SQLITE_AMALGAMATION)
192991 #include "sqlite3rtree.h"
192992 typedef sqlite3_int64 i64;
192993 typedef sqlite3_uint64 u64;
192994 typedef unsigned char u8;
192995 typedef unsigned short u16;
@@ -192921,11 +192998,21 @@
192998 # define NDEBUG 1
192999 #endif
193000 #if defined(NDEBUG) && defined(SQLITE_DEBUG)
193001 # undef NDEBUG
193002 #endif
193003 #if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
193004 # define ALWAYS(X) (1)
193005 # define NEVER(X) (0)
193006 #elif !defined(NDEBUG)
193007 # define ALWAYS(X) ((X)?1:(assert(0),0))
193008 # define NEVER(X) ((X)?(assert(0),1):0)
193009 #else
193010 # define ALWAYS(X) (X)
193011 # define NEVER(X) (X)
193012 #endif
193013 #endif /* !defined(SQLITE_AMALGAMATION) */
193014
193015 /* #include <string.h> */
193016 /* #include <stdio.h> */
193017 /* #include <assert.h> */
193018 /* #include <stdlib.h> */
@@ -192979,11 +193066,13 @@
193066 u8 nDim2; /* Twice the number of dimensions */
193067 u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
193068 u8 nBytesPerCell; /* Bytes consumed per cell */
193069 u8 inWrTrans; /* True if inside write transaction */
193070 u8 nAux; /* # of auxiliary columns in %_rowid */
193071 #ifdef SQLITE_ENABLE_GEOPOLY
193072 u8 nAuxNotNull; /* Number of initial not-null aux columns */
193073 #endif
193074 #ifdef SQLITE_DEBUG
193075 u8 bCorrupt; /* Shadow table corruption detected */
193076 #endif
193077 int iDepth; /* Current depth of the r-tree structure */
193078 char *zDb; /* Name of database containing r-tree table */
@@ -193510,22 +193599,10 @@
193599 pRtree->pNodeBlob = 0;
193600 sqlite3_blob_close(pBlob);
193601 }
193602 }
193603
 
 
 
 
 
 
 
 
 
 
 
 
193604 /*
193605 ** Obtain a reference to an r-tree node.
193606 */
193607 static int nodeAcquire(
193608 Rtree *pRtree, /* R-tree structure */
@@ -193538,18 +193615,11 @@
193615
193616 /* Check if the requested node is already in the hash table. If so,
193617 ** increase its reference count and return it.
193618 */
193619 if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){
193620 if( pParent && pParent!=pNode->pParent ){
 
 
 
 
 
 
 
193621 RTREE_IS_CORRUPT(pRtree);
193622 return SQLITE_CORRUPT_VTAB;
193623 }
193624 pNode->nRef++;
193625 *ppNode = pNode;
@@ -193603,11 +193673,11 @@
193673 ** of the r-tree structure. A height of zero means all data is stored on
193674 ** the root node. A height of one means the children of the root node
193675 ** are the leaves, and so on. If the depth as specified on the root node
193676 ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt.
193677 */
193678 if( rc==SQLITE_OK && pNode && iNode==1 ){
193679 pRtree->iDepth = readInt16(pNode->zData);
193680 if( pRtree->iDepth>RTREE_MAX_DEPTH ){
193681 rc = SQLITE_CORRUPT_VTAB;
193682 RTREE_IS_CORRUPT(pRtree);
193683 }
@@ -194209,15 +194279,16 @@
194279 ** Return the index of the cell containing a pointer to node pNode
194280 ** in its parent. If pNode is the root node, return -1.
194281 */
194282 static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){
194283 RtreeNode *pParent = pNode->pParent;
194284 if( ALWAYS(pParent) ){
194285 return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex);
194286 }else{
194287 *piIndex = -1;
194288 return SQLITE_OK;
194289 }
 
 
194290 }
194291
194292 /*
194293 ** Compare two search points. Return negative, zero, or positive if the first
194294 ** is less than, equal to, or greater than the second.
@@ -194336,11 +194407,12 @@
194407 if( pCur->bPoint ){
194408 int ii;
194409 pNew = rtreeEnqueue(pCur, rScore, iLevel);
194410 if( pNew==0 ) return 0;
194411 ii = (int)(pNew - pCur->aPoint) + 1;
194412 assert( ii==1 );
194413 if( ALWAYS(ii<RTREE_CACHE_SZ) ){
194414 assert( pCur->aNode[ii]==0 );
194415 pCur->aNode[ii] = pCur->aNode[0];
194416 }else{
194417 nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]);
194418 }
@@ -194397,11 +194469,11 @@
194469 p->aNode[i] = 0;
194470 }
194471 if( p->bPoint ){
194472 p->anQueue[p->sPoint.iLevel]--;
194473 p->bPoint = 0;
194474 }else if( ALWAYS(p->nPoint) ){
194475 p->anQueue[p->aPoint[0].iLevel]--;
194476 n = --p->nPoint;
194477 p->aPoint[0] = p->aPoint[n];
194478 if( n<RTREE_CACHE_SZ-1 ){
194479 p->aNode[1] = p->aNode[n+1];
@@ -194538,11 +194610,11 @@
194610 static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
194611 RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
194612 RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
194613 int rc = SQLITE_OK;
194614 RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194615 if( rc==SQLITE_OK && ALWAYS(p) ){
194616 *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
194617 }
194618 return rc;
194619 }
194620
@@ -194556,11 +194628,11 @@
194628 RtreeCoord c;
194629 int rc = SQLITE_OK;
194630 RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
194631
194632 if( rc ) return rc;
194633 if( NEVER(p==0) ) return SQLITE_OK;
194634 if( i==0 ){
194635 sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
194636 }else if( i<=pRtree->nDim2 ){
194637 nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
194638 #ifndef SQLITE_RTREE_INT_ONLY
@@ -194755,12 +194827,15 @@
194827 }
194828 }
194829 }
194830 if( rc==SQLITE_OK ){
194831 RtreeSearchPoint *pNew;
194832 assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */
194833 pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
194834 if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */
194835 return SQLITE_NOMEM;
194836 }
194837 pNew->id = 1;
194838 pNew->iCell = 0;
194839 pNew->eWithin = PARTLY_WITHIN;
194840 assert( pCsr->bPoint==1 );
194841 pCsr->aNode[0] = pRoot;
@@ -194833,11 +194908,11 @@
194908 assert( pIdxInfo->idxStr==0 );
194909 for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
194910 struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
194911
194912 if( bMatch==0 && p->usable
194913 && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
194914 ){
194915 /* We have an equality constraint on the rowid. Use strategy 1. */
194916 int jj;
194917 for(jj=0; jj<ii; jj++){
194918 pIdxInfo->aConstraintUsage[jj].argvIndex = 0;
@@ -195086,16 +195161,23 @@
195161 RtreeNode *pNode, /* Adjust ancestry of this node. */
195162 RtreeCell *pCell /* This cell was just inserted */
195163 ){
195164 RtreeNode *p = pNode;
195165 int cnt = 0;
195166 int rc;
195167 while( p->pParent ){
195168 RtreeNode *pParent = p->pParent;
195169 RtreeCell cell;
195170 int iCell;
195171
195172 cnt++;
195173 if( NEVER(cnt>100) ){
195174 RTREE_IS_CORRUPT(pRtree);
195175 return SQLITE_CORRUPT_VTAB;
195176 }
195177 rc = nodeParentIndex(pRtree, p, &iCell);
195178 if( NEVER(rc!=SQLITE_OK) ){
195179 RTREE_IS_CORRUPT(pRtree);
195180 return SQLITE_CORRUPT_VTAB;
195181 }
195182
195183 nodeGetCell(pRtree, pParent, iCell, &cell);
@@ -195475,15 +195557,16 @@
195557 }
195558 }else{
195559 RtreeNode *pParent = pLeft->pParent;
195560 int iCell;
195561 rc = nodeParentIndex(pRtree, pLeft, &iCell);
195562 if( ALWAYS(rc==SQLITE_OK) ){
195563 nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell);
195564 rc = AdjustTree(pRtree, pParent, &leftbbox);
195565 assert( rc==SQLITE_OK );
195566 }
195567 if( NEVER(rc!=SQLITE_OK) ){
195568 goto splitnode_out;
195569 }
195570 }
195571 if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){
195572 goto splitnode_out;
@@ -195554,11 +195637,11 @@
195637 ** want to do this as it leads to a memory leak when trying to delete
195638 ** the referenced counted node structures.
195639 */
195640 iNode = sqlite3_column_int64(pRtree->pReadParent, 0);
195641 for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent);
195642 if( pTest==0 ){
195643 rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent);
195644 }
195645 }
195646 rc = sqlite3_reset(pRtree->pReadParent);
195647 if( rc==SQLITE_OK ) rc = rc2;
@@ -195585,10 +195668,11 @@
195668 rc = nodeParentIndex(pRtree, pNode, &iCell);
195669 if( rc==SQLITE_OK ){
195670 pParent = pNode->pParent;
195671 pNode->pParent = 0;
195672 rc = deleteCell(pRtree, pParent, iCell, iHeight+1);
195673 testcase( rc!=SQLITE_OK );
195674 }
195675 rc2 = nodeRelease(pRtree, pParent);
195676 if( rc==SQLITE_OK ){
195677 rc = rc2;
195678 }
@@ -195807,11 +195891,11 @@
195891 pRtree->iReinsertHeight = iHeight;
195892 rc = Reinsert(pRtree, pNode, pCell, iHeight);
195893 }
195894 }else{
195895 rc = AdjustTree(pRtree, pNode, pCell);
195896 if( ALWAYS(rc==SQLITE_OK) ){
195897 if( iHeight==0 ){
195898 rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode);
195899 }else{
195900 rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode);
195901 }
@@ -195913,11 +195997,11 @@
195997 */
195998 if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
195999 int rc2;
196000 RtreeNode *pChild = 0;
196001 i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
196002 rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */
196003 if( rc==SQLITE_OK ){
196004 rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
196005 }
196006 rc2 = nodeRelease(pRtree, pChild);
196007 if( rc==SQLITE_OK ) rc = rc2;
@@ -196248,11 +196332,11 @@
196332 static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
196333 const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'";
196334 char *zSql;
196335 sqlite3_stmt *p;
196336 int rc;
196337 i64 nRow = RTREE_MIN_ROWEST;
196338
196339 rc = sqlite3_table_column_metadata(
196340 db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
196341 );
196342 if( rc!=SQLITE_OK ){
@@ -196265,24 +196349,14 @@
196349 }else{
196350 rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0);
196351 if( rc==SQLITE_OK ){
196352 if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0);
196353 rc = sqlite3_finalize(p);
 
 
 
 
 
 
 
 
 
 
196354 }
196355 sqlite3_free(zSql);
196356 }
196357 pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
196358 return rc;
196359 }
196360
196361
196362 /*
@@ -196428,13 +196502,16 @@
196502 int ii;
196503 char *zSql;
196504 sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
196505 for(ii=0; ii<pRtree->nAux; ii++){
196506 if( ii ) sqlite3_str_append(p, ",", 1);
196507 #ifdef SQLITE_ENABLE_GEOPOLY
196508 if( ii<pRtree->nAuxNotNull ){
196509 sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii);
196510 }else
196511 #endif
196512 {
196513 sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
196514 }
196515 }
196516 sqlite3_str_appendf(p, " WHERE rowid=?1");
196517 zSql = sqlite3_str_finish(p);
@@ -197109,12 +197186,14 @@
197186 if( check.rc==SQLITE_OK ){
197187 pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
197188 if( pStmt ){
197189 nAux = sqlite3_column_count(pStmt) - 2;
197190 sqlite3_finalize(pStmt);
197191 }else
197192 if( check.rc!=SQLITE_NOMEM ){
197193 check.rc = SQLITE_OK;
197194 }
 
197195 }
197196
197197 /* Find number of dimensions in the rtree table. */
197198 pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
197199 if( pStmt ){
@@ -199185,11 +199264,14 @@
199264 ){
199265 RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
199266
199267 /* Allocate and populate the context object. */
199268 pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
199269 if( !pGeomCtx ){
199270 if( xDestructor ) xDestructor(pContext);
199271 return SQLITE_NOMEM;
199272 }
199273 pGeomCtx->xGeom = 0;
199274 pGeomCtx->xQueryFunc = xQueryFunc;
199275 pGeomCtx->xDestructor = xDestructor;
199276 pGeomCtx->pContext = pContext;
199277 return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
@@ -222468,10 +222550,46 @@
222550 if( p->pStruct!=(Fts5Structure*)pStruct ){
222551 return SQLITE_ABORT;
222552 }
222553 return SQLITE_OK;
222554 }
222555
222556 /*
222557 ** Ensure that structure object (*pp) is writable.
222558 **
222559 ** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If
222560 ** an error occurs, (*pRc) is set to an SQLite error code before returning.
222561 */
222562 static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){
222563 Fts5Structure *p = *pp;
222564 if( *pRc==SQLITE_OK && p->nRef>1 ){
222565 int nByte = sizeof(Fts5Structure)+(p->nLevel-1)*sizeof(Fts5StructureLevel);
222566 Fts5Structure *pNew;
222567 pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte);
222568 if( pNew ){
222569 int i;
222570 memcpy(pNew, p, nByte);
222571 for(i=0; i<p->nLevel; i++) pNew->aLevel[i].aSeg = 0;
222572 for(i=0; i<p->nLevel; i++){
222573 Fts5StructureLevel *pLvl = &pNew->aLevel[i];
222574 nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg;
222575 pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte);
222576 if( pLvl->aSeg==0 ){
222577 for(i=0; i<p->nLevel; i++){
222578 sqlite3_free(pNew->aLevel[i].aSeg);
222579 }
222580 sqlite3_free(pNew);
222581 return;
222582 }
222583 memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte);
222584 }
222585 p->nRef--;
222586 pNew->nRef = 1;
222587 }
222588 *pp = pNew;
222589 }
222590 }
222591
222592 /*
222593 ** Deserialize and return the structure record currently stored in serialized
222594 ** form within buffer pData/nData.
222595 **
@@ -222570,13 +222688,15 @@
222688 *ppOut = pRet;
222689 return rc;
222690 }
222691
222692 /*
222693 ** Add a level to the Fts5Structure.aLevel[] array of structure object
222694 ** (*ppStruct).
222695 */
222696 static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
222697 fts5StructureMakeWritable(pRc, ppStruct);
222698 if( *pRc==SQLITE_OK ){
222699 Fts5Structure *pStruct = *ppStruct;
222700 int nLevel = pStruct->nLevel;
222701 sqlite3_int64 nByte = (
222702 sizeof(Fts5Structure) + /* Main structure */
@@ -231175,11 +231295,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
+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-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1"
151
+#define SQLITE_SOURCE_ID "2021-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
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-06 11:44:19 b3cfe23bec0b95ca673802526704200e2396df715fdded72aa71addd7f47e0e1"
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-09-22 14:43:35 d678ecca02698753d1b33e072566112e94ea36d0d3a8f4a24d2b09d131968d88"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
+131 -52
--- src/style.chat.css
+++ src/style.chat.css
@@ -9,14 +9,18 @@
99
border: none;
1010
display: flex;
1111
flex-direction: column;
1212
border: none;
1313
align-items: flex-start;
14
+}
15
+body.chat .message-widget:last-of-type {
16
+ /* Latest message: reduce bottom gap */
17
+ margin-bottom: 0.1em;
1418
}
1519
body.chat.my-messages-right .message-widget.mine {
1620
/* Right-aligns a user's own chat messages, similar to how
17
- most mobile messaging apps do it. */
21
+ most/some mobile messaging apps do it. */
1822
align-items: flex-end;
1923
}
2024
body.chat.my-messages-right .message-widget.notification {
2125
/* Center-aligns a system-level notification message. */
2226
align-items: center;
@@ -77,10 +81,12 @@
7781
display: flex;
7882
flex-direction: column;
7983
align-items: stretch;
8084
padding: 0.25em;
8185
margin-top: 0.25em;
86
+ border: 1px outset;
87
+ border-radius: 0.5em;
8288
}
8389
/* Full message timestamps. */
8490
body.chat .chat-message-popup > span { white-space: nowrap; }
8591
/* Container for the message deletion buttons. */
8692
body.chat .chat-message-popup > .toolbar {
@@ -132,53 +138,14 @@
132138
flex-direction: column;
133139
align-items: stretch;
134140
padding: 0.25em;
135141
z-index: 200;
136142
}
137
-body.chat .chat-settings-popup > span {
138
- vertical-align: middle;
139
-}
140
-body.chat .chat-settings-popup > span.menu-entry{
141
- white-space: nowrap;
142
- cursor: pointer;
143
- border: 1px solid;
144
- border-radius: 0.25em;
145
- padding: 0.25em 0.5em;
146
- display: flex;
147
- flex-direction: row;
148
- align-items: center;
149
- justify-content: space-between;
150
-}
151
-body.chat .chat-settings-popup > span.menu-entry:hover {
152
-}
153
-body.chat .chat-settings-popup > span.menu-entry > .help-buttonlet {
154
- vertical-align: middle;
155
-}
156
-body.chat .chat-settings-popup > span.menu-entry > span.button {
157
- margin: 0.25em 0.2em;
158
- padding: 0.25em;
159
- flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
160
-}
161
-body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
162
- cursor: inherit;
163
-}
164
-body.chat .chat-settings-popup > select.menu-entry {
165
- flex: 1 1 auto;
166
- padding: 0;
167
- cursor: pointer;
168
- min-height: 2.5em;
169
- border-radius: 0.25em;
170
-}
171
-body.chat .chat-settings-popup > select.menu-entry > option {
172
- /*Recall that many browsers don't allow styling of OPTION
173
- elements, or allow only very limited styling.*/
174
-}
175143
176144
/** Container for the list of /chat messages. */
177145
body.chat #chat-messages-wrapper {
178146
overflow: auto;
179
- flex: 2 1 auto;
180147
padding: 0 0.25em;
181148
}
182149
body.chat #chat-messages-wrapper.loading > * {
183150
/* An attempt at reducing flicker when loading lots of messages. */
184151
visibility: hidden;
@@ -199,16 +166,14 @@
199166
}
200167
/* Wrapper for /chat user input controls */
201168
body.chat #chat-input-area {
202169
display: flex;
203170
flex-direction: column;
204
- padding: 0.5em 0 0 0;
205
- border-bottom: none;
206
- border-top: 1px solid black;
207
- margin: 0.5em 0;
171
+ padding: 0;
172
+ margin: 0;
208173
position: initial /*sticky currently disabled due to scrolling-related issues*/;
209
- bottom: 0;
174
+ /*bottom: 0;*/
210175
}
211176
body.chat:not(.chat-only-mode) #chat-input-area{
212177
/* Safari user reports that 2em is necessary to keep the file selection
213178
widget from overlapping the page footer, whereas a margin of 0 is fine
214179
for FF/Chrome (and 2em is a *huge* waste of space for those). */
@@ -234,10 +199,11 @@
234199
body.chat #chat-input-line.single-line #chat-edit-buttons {
235200
flex-direction: row;
236201
}
237202
body.chat #chat-edit-buttons > * {
238203
flex: 1 1 auto;
204
+ padding: initial/*some skins mess this up for buttons*/;
239205
}
240206
body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
241207
max-width: 4em;
242208
margin: 0.25em;
243209
}
@@ -265,17 +231,17 @@
265231
body.chat #chat-input-file-area {
266232
display: flex;
267233
flex-direction: row;
268234
align-items: center;
269235
flex-wrap: wrap;
270
- margin-bottom: 0.25em 0 0 0 /* avoid nudging input area */;
236
+ margin: 0.25em 0 0 0 /* avoid nudging input area */;
271237
}
272238
body.chat #chat-input-file-area > .file-selection-wrapper {
273239
align-self: flex-start;
274240
margin-right: 0.5em;
275241
flex: 0 1 auto;
276
- padding: 0.25em 0.25em 0.25em 0;
242
+ padding: 0.25em 0.5em;
277243
white-space: nowrap;
278244
}
279245
body.chat #chat-input-file-area .file-selection-wrapper > * {
280246
vertical-align: middle;
281247
margin: 0;
@@ -303,39 +269,48 @@
303269
304270
body.chat #chat-drop-details img {
305271
max-width: 45%;
306272
max-height: 45%;
307273
}
308
-
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
+}
309279
body.chat #chat-config,
310280
body.chat #chat-preview {
311281
/* /chat configuration widget */
312282
display: flex;
313283
flex-direction: column;
314284
overflow: auto;
315
- flex: 2 1 auto;
316285
padding: 0;
317286
margin: 0;
318287
align-items: stretch;
319288
min-height: 6em;
320289
}
321290
body.chat #chat-config #chat-config-options {
322291
/* /chat config options go here */
323292
flex: 1 1 auto;
324293
display: flex;
325
- flex-direction: column;
294
+ flex-direction: column-reverse;
326295
overflow: auto;
327296
}
328297
body.chat #chat-config #chat-config-options .menu-entry {
329298
display: flex;
330299
align-items: center;
331300
flex-direction: row;
332
- flex-wrap: wrap;
301
+ flex-wrap: nowrap;
333302
padding: 1em;
334303
}
335
-body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
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 {
336308
margin-right: 1em;
309
+}
310
+body.chat #chat-config #chat-config-options .menu-entry > label:first-child {
311
+ margin-right: 0.5em;
337312
}
338313
body.chat #chat-preview #chat-preview-content {
339314
overflow: auto;
340315
flex: 1 1 auto;
341316
padding: 0.5em;
@@ -354,5 +329,109 @@
354329
body.chat #chat-preview #chat-preview-buttons > button {
355330
padding: 0.5em;
356331
flex: 0 1 auto;
357332
margin: 0.25em 0;
358333
}
334
+
335
+body.chat #chat-user-list-wrapper {
336
+ /* Safari can't do fieldsets right, so we emulate one. */
337
+ border-radius: 0.5em;
338
+ margin: 1em 0 0.2em 0;
339
+ padding: 0 0.5em;
340
+ border-style: inset;
341
+ border-width: 0 1px 1px 1px/*else collides with the LEGEND*/;
342
+}
343
+body.chat #chat-user-list-wrapper.collapsed {
344
+ padding: 0;
345
+}
346
+body.chat #chat-user-list-wrapper > .legend {
347
+ font-weight: initial;
348
+ padding: 0 0.5em 0 0.5em;
349
+ position: relative;
350
+ top: -1.75ex/* place it like a fieldset legend */;
351
+ cursor: pointer;
352
+}
353
+body.chat #chat-user-list-wrapper > .legend > * {
354
+ vertical-align: middle;
355
+}
356
+body.chat #chat-user-list-wrapper > .legend > *:nth-child(2){
357
+ /* Title label */
358
+ opacity: 0.6;
359
+ font-size: 0.8em;
360
+}
361
+body.chat #chat-user-list-wrapper.collapsed > .legend > *:nth-child(2)::after {
362
+ content: " (tap to toggle)";
363
+}
364
+body.chat #chat-user-list-wrapper .help-buttonlet {
365
+ margin: 0;
366
+}
367
+body.chat #chat-user-list-wrapper.collapsed #chat-user-list {
368
+ position: absolute !important;
369
+ opacity: 0 !important;
370
+ pointer-events: none !important;
371
+ display: none !important;
372
+}
373
+body.chat #chat-user-list {
374
+ margin-top: -1.25ex;
375
+ display: flex;
376
+ flex-direction: row;
377
+ flex-wrap: wrap;
378
+ align-items: center;
379
+}
380
+body.chat #chat-user-list .chat-user {
381
+ margin: 0.2em;
382
+ padding: 0.1em 0.5em 0.2em 0.5em;
383
+ border-radius: 0.5em;
384
+ cursor: pointer;
385
+ text-align: center;
386
+ white-space: pre;
387
+}
388
+body.chat #chat-user-list .timestamp {
389
+ font-size: 85%;
390
+ font-family: monospace;
391
+}
392
+body.chat #chat-user-list:not(.timestamps) .timestamp {
393
+ display: none;
394
+}
395
+body.chat #chat-user-list .chat-user.selected {
396
+ font-weight: bold;
397
+ text-decoration: underline;
398
+}
399
+
400
+body.chat .anim-rotate-360 {
401
+ animation: rotate-360 750ms linear;
402
+}
403
+@keyframes rotate-360 {
404
+ from { transform: rotate(0deg); }
405
+ to { transform: rotate(360deg); }
406
+}
407
+body.chat .anim-flip-h {
408
+ animation: flip-h 750ms linear;
409
+}
410
+@keyframes flip-h{
411
+ from { transform: rotateY(0deg); }
412
+ to { transform: rotateY(360deg); }
413
+}
414
+body.chat .anim-flip-v {
415
+ animation: flip-v 750ms linear;
416
+}
417
+@keyframes flip-v{
418
+ from { transform: rotateX(0deg); }
419
+ to { transform: rotateX(360deg); }
420
+}
421
+body.chat .anim-fade-in {
422
+ animation: fade-in 750ms linear;
423
+}
424
+body.chat .anim-fade-in-fast {
425
+ animation: fade-in 350ms linear;
426
+}
427
+@keyframes fade-in {
428
+ from { opacity: 0; }
429
+ to { opacity: 1; }
430
+}
431
+body.chat .anim-fade-out-fast {
432
+ animation: fade-out 250ms linear;
433
+}
434
+@keyframes fade-out {
435
+ from { opacity: 1; }
436
+ to { opacity: 0; }
437
+}
359438
--- src/style.chat.css
+++ src/style.chat.css
@@ -9,14 +9,18 @@
9 border: none;
10 display: flex;
11 flex-direction: column;
12 border: none;
13 align-items: flex-start;
 
 
 
 
14 }
15 body.chat.my-messages-right .message-widget.mine {
16 /* Right-aligns a user's own chat messages, similar to how
17 most mobile messaging apps do it. */
18 align-items: flex-end;
19 }
20 body.chat.my-messages-right .message-widget.notification {
21 /* Center-aligns a system-level notification message. */
22 align-items: center;
@@ -77,10 +81,12 @@
77 display: flex;
78 flex-direction: column;
79 align-items: stretch;
80 padding: 0.25em;
81 margin-top: 0.25em;
 
 
82 }
83 /* Full message timestamps. */
84 body.chat .chat-message-popup > span { white-space: nowrap; }
85 /* Container for the message deletion buttons. */
86 body.chat .chat-message-popup > .toolbar {
@@ -132,53 +138,14 @@
132 flex-direction: column;
133 align-items: stretch;
134 padding: 0.25em;
135 z-index: 200;
136 }
137 body.chat .chat-settings-popup > span {
138 vertical-align: middle;
139 }
140 body.chat .chat-settings-popup > span.menu-entry{
141 white-space: nowrap;
142 cursor: pointer;
143 border: 1px solid;
144 border-radius: 0.25em;
145 padding: 0.25em 0.5em;
146 display: flex;
147 flex-direction: row;
148 align-items: center;
149 justify-content: space-between;
150 }
151 body.chat .chat-settings-popup > span.menu-entry:hover {
152 }
153 body.chat .chat-settings-popup > span.menu-entry > .help-buttonlet {
154 vertical-align: middle;
155 }
156 body.chat .chat-settings-popup > span.menu-entry > span.button {
157 margin: 0.25em 0.2em;
158 padding: 0.25em;
159 flex: 1 1 auto/*eliminates dead no-click zones on the right*/;
160 }
161 body.chat .chat-settings-popup > span.menu-entry > input[type=checkbox] {
162 cursor: inherit;
163 }
164 body.chat .chat-settings-popup > select.menu-entry {
165 flex: 1 1 auto;
166 padding: 0;
167 cursor: pointer;
168 min-height: 2.5em;
169 border-radius: 0.25em;
170 }
171 body.chat .chat-settings-popup > select.menu-entry > option {
172 /*Recall that many browsers don't allow styling of OPTION
173 elements, or allow only very limited styling.*/
174 }
175
176 /** Container for the list of /chat messages. */
177 body.chat #chat-messages-wrapper {
178 overflow: auto;
179 flex: 2 1 auto;
180 padding: 0 0.25em;
181 }
182 body.chat #chat-messages-wrapper.loading > * {
183 /* An attempt at reducing flicker when loading lots of messages. */
184 visibility: hidden;
@@ -199,16 +166,14 @@
199 }
200 /* Wrapper for /chat user input controls */
201 body.chat #chat-input-area {
202 display: flex;
203 flex-direction: column;
204 padding: 0.5em 0 0 0;
205 border-bottom: none;
206 border-top: 1px solid black;
207 margin: 0.5em 0;
208 position: initial /*sticky currently disabled due to scrolling-related issues*/;
209 bottom: 0;
210 }
211 body.chat:not(.chat-only-mode) #chat-input-area{
212 /* Safari user reports that 2em is necessary to keep the file selection
213 widget from overlapping the page footer, whereas a margin of 0 is fine
214 for FF/Chrome (and 2em is a *huge* waste of space for those). */
@@ -234,10 +199,11 @@
234 body.chat #chat-input-line.single-line #chat-edit-buttons {
235 flex-direction: row;
236 }
237 body.chat #chat-edit-buttons > * {
238 flex: 1 1 auto;
 
239 }
240 body.chat #chat-input-line:not(.single-line) #chat-edit-buttons > * {
241 max-width: 4em;
242 margin: 0.25em;
243 }
@@ -265,17 +231,17 @@
265 body.chat #chat-input-file-area {
266 display: flex;
267 flex-direction: row;
268 align-items: center;
269 flex-wrap: wrap;
270 margin-bottom: 0.25em 0 0 0 /* avoid nudging input area */;
271 }
272 body.chat #chat-input-file-area > .file-selection-wrapper {
273 align-self: flex-start;
274 margin-right: 0.5em;
275 flex: 0 1 auto;
276 padding: 0.25em 0.25em 0.25em 0;
277 white-space: nowrap;
278 }
279 body.chat #chat-input-file-area .file-selection-wrapper > * {
280 vertical-align: middle;
281 margin: 0;
@@ -303,39 +269,48 @@
303
304 body.chat #chat-drop-details img {
305 max-width: 45%;
306 max-height: 45%;
307 }
308
 
 
 
 
309 body.chat #chat-config,
310 body.chat #chat-preview {
311 /* /chat configuration widget */
312 display: flex;
313 flex-direction: column;
314 overflow: auto;
315 flex: 2 1 auto;
316 padding: 0;
317 margin: 0;
318 align-items: stretch;
319 min-height: 6em;
320 }
321 body.chat #chat-config #chat-config-options {
322 /* /chat config options go here */
323 flex: 1 1 auto;
324 display: flex;
325 flex-direction: column;
326 overflow: auto;
327 }
328 body.chat #chat-config #chat-config-options .menu-entry {
329 display: flex;
330 align-items: center;
331 flex-direction: row;
332 flex-wrap: wrap;
333 padding: 1em;
334 }
335 body.chat #chat-config #chat-config-options .menu-entry > *:first-child {
 
 
 
336 margin-right: 1em;
 
 
 
337 }
338 body.chat #chat-preview #chat-preview-content {
339 overflow: auto;
340 flex: 1 1 auto;
341 padding: 0.5em;
@@ -354,5 +329,109 @@
354 body.chat #chat-preview #chat-preview-buttons > button {
355 padding: 0.5em;
356 flex: 0 1 auto;
357 margin: 0.25em 0;
358 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
--- src/style.chat.css
+++ src/style.chat.css
@@ -9,14 +9,18 @@
9 border: none;
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 {
20 /* Right-aligns a user's own chat messages, similar to how
21 most/some mobile messaging apps do it. */
22 align-items: flex-end;
23 }
24 body.chat.my-messages-right .message-widget.notification {
25 /* Center-aligns a system-level notification message. */
26 align-items: center;
@@ -77,10 +81,12 @@
81 display: flex;
82 flex-direction: column;
83 align-items: stretch;
84 padding: 0.25em;
85 margin-top: 0.25em;
86 border: 1px outset;
87 border-radius: 0.5em;
88 }
89 /* Full message timestamps. */
90 body.chat .chat-message-popup > span { white-space: nowrap; }
91 /* Container for the message deletion buttons. */
92 body.chat .chat-message-popup > .toolbar {
@@ -132,53 +138,14 @@
138 flex-direction: column;
139 align-items: stretch;
140 padding: 0.25em;
141 z-index: 200;
142 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
144 /** Container for the list of /chat messages. */
145 body.chat #chat-messages-wrapper {
146 overflow: auto;
 
147 padding: 0 0.25em;
148 }
149 body.chat #chat-messages-wrapper.loading > * {
150 /* An attempt at reducing flicker when loading lots of messages. */
151 visibility: hidden;
@@ -199,16 +166,14 @@
166 }
167 /* Wrapper for /chat user input controls */
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). */
@@ -234,10 +199,11 @@
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 }
@@ -265,17 +231,17 @@
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;
@@ -303,39 +269,48 @@
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 */
282 display: flex;
283 flex-direction: column;
284 overflow: auto;
 
285 padding: 0;
286 margin: 0;
287 align-items: stretch;
288 min-height: 6em;
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;
@@ -354,5 +329,109 @@
329 body.chat #chat-preview #chat-preview-buttons > button {
330 padding: 0.5em;
331 flex: 0 1 auto;
332 margin: 0.25em 0;
333 }
334
335 body.chat #chat-user-list-wrapper {
336 /* Safari can't do fieldsets right, so we emulate one. */
337 border-radius: 0.5em;
338 margin: 1em 0 0.2em 0;
339 padding: 0 0.5em;
340 border-style: inset;
341 border-width: 0 1px 1px 1px/*else collides with the LEGEND*/;
342 }
343 body.chat #chat-user-list-wrapper.collapsed {
344 padding: 0;
345 }
346 body.chat #chat-user-list-wrapper > .legend {
347 font-weight: initial;
348 padding: 0 0.5em 0 0.5em;
349 position: relative;
350 top: -1.75ex/* place it like a fieldset legend */;
351 cursor: pointer;
352 }
353 body.chat #chat-user-list-wrapper > .legend > * {
354 vertical-align: middle;
355 }
356 body.chat #chat-user-list-wrapper > .legend > *:nth-child(2){
357 /* Title label */
358 opacity: 0.6;
359 font-size: 0.8em;
360 }
361 body.chat #chat-user-list-wrapper.collapsed > .legend > *:nth-child(2)::after {
362 content: " (tap to toggle)";
363 }
364 body.chat #chat-user-list-wrapper .help-buttonlet {
365 margin: 0;
366 }
367 body.chat #chat-user-list-wrapper.collapsed #chat-user-list {
368 position: absolute !important;
369 opacity: 0 !important;
370 pointer-events: none !important;
371 display: none !important;
372 }
373 body.chat #chat-user-list {
374 margin-top: -1.25ex;
375 display: flex;
376 flex-direction: row;
377 flex-wrap: wrap;
378 align-items: center;
379 }
380 body.chat #chat-user-list .chat-user {
381 margin: 0.2em;
382 padding: 0.1em 0.5em 0.2em 0.5em;
383 border-radius: 0.5em;
384 cursor: pointer;
385 text-align: center;
386 white-space: pre;
387 }
388 body.chat #chat-user-list .timestamp {
389 font-size: 85%;
390 font-family: monospace;
391 }
392 body.chat #chat-user-list:not(.timestamps) .timestamp {
393 display: none;
394 }
395 body.chat #chat-user-list .chat-user.selected {
396 font-weight: bold;
397 text-decoration: underline;
398 }
399
400 body.chat .anim-rotate-360 {
401 animation: rotate-360 750ms linear;
402 }
403 @keyframes rotate-360 {
404 from { transform: rotate(0deg); }
405 to { transform: rotate(360deg); }
406 }
407 body.chat .anim-flip-h {
408 animation: flip-h 750ms linear;
409 }
410 @keyframes flip-h{
411 from { transform: rotateY(0deg); }
412 to { transform: rotateY(360deg); }
413 }
414 body.chat .anim-flip-v {
415 animation: flip-v 750ms linear;
416 }
417 @keyframes flip-v{
418 from { transform: rotateX(0deg); }
419 to { transform: rotateX(360deg); }
420 }
421 body.chat .anim-fade-in {
422 animation: fade-in 750ms linear;
423 }
424 body.chat .anim-fade-in-fast {
425 animation: fade-in 350ms linear;
426 }
427 @keyframes fade-in {
428 from { opacity: 0; }
429 to { opacity: 1; }
430 }
431 body.chat .anim-fade-out-fast {
432 animation: fade-out 250ms linear;
433 }
434 @keyframes fade-out {
435 from { opacity: 1; }
436 to { opacity: 0; }
437 }
438
+14 -5
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
18881888
}
18891889
18901890
/*
18911891
** COMMAND: test-markdown-render
18921892
**
1893
-** Usage: %fossil test-markdown-render FILE ...
1893
+** Usage: %fossil test-markdown-render TEXT ...
18941894
**
1895
-** Render markdown in FILE as HTML on stdout.
1895
+** Render markdown in TEXT as HTML on stdout, where TEXT
1896
+** may be a file name or a markdown-formatted string.
1897
+**
18961898
** Options:
18971899
**
18981900
** --safe Restrict the output to use only "safe" HTML
18991901
*/
19001902
void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
19041906
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
19051907
bSafe = find_option("safe",0,0)!=0;
19061908
verify_all_options();
19071909
for(i=2; i<g.argc; i++){
19081910
blob_zero(&out);
1909
- blob_read_from_file(&in, g.argv[i], ExtFILE);
1910
- if( g.argc>3 ){
1911
- fossil_print("<!------ %h ------->\n", g.argv[i]);
1911
+ if(file_isfile(g.argv[i], ExtFILE)){
1912
+ blob_read_from_file(&in, g.argv[i], ExtFILE);
1913
+ if( g.argc>3 ){
1914
+ fossil_print("<!------ %h ------->\n", g.argv[i]);
1915
+ }
1916
+ }else{
1917
+ blob_init(&in, g.argv[i], -1);
1918
+ if( g.argc>3 ){
1919
+ fossil_print("<!------ script #%d ------->\n", i-1);
1920
+ }
19121921
}
19131922
markdown_to_html(&in, 0, &out);
19141923
safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
19151924
safe_html(&out);
19161925
blob_write_to_file(&out, "-");
19171926
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
1888 }
1889
1890 /*
1891 ** COMMAND: test-markdown-render
1892 **
1893 ** Usage: %fossil test-markdown-render FILE ...
1894 **
1895 ** Render markdown in FILE as HTML on stdout.
 
 
1896 ** Options:
1897 **
1898 ** --safe Restrict the output to use only "safe" HTML
1899 */
1900 void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
1904 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1905 bSafe = find_option("safe",0,0)!=0;
1906 verify_all_options();
1907 for(i=2; i<g.argc; i++){
1908 blob_zero(&out);
1909 blob_read_from_file(&in, g.argv[i], ExtFILE);
1910 if( g.argc>3 ){
1911 fossil_print("<!------ %h ------->\n", g.argv[i]);
 
 
 
 
 
 
 
1912 }
1913 markdown_to_html(&in, 0, &out);
1914 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
1915 safe_html(&out);
1916 blob_write_to_file(&out, "-");
1917
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
1888 }
1889
1890 /*
1891 ** COMMAND: test-markdown-render
1892 **
1893 ** Usage: %fossil test-markdown-render TEXT ...
1894 **
1895 ** Render markdown in TEXT as HTML on stdout, where TEXT
1896 ** may be a file name or a markdown-formatted string.
1897 **
1898 ** Options:
1899 **
1900 ** --safe Restrict the output to use only "safe" HTML
1901 */
1902 void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
1906 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1907 bSafe = find_option("safe",0,0)!=0;
1908 verify_all_options();
1909 for(i=2; i<g.argc; i++){
1910 blob_zero(&out);
1911 if(file_isfile(g.argv[i], ExtFILE)){
1912 blob_read_from_file(&in, g.argv[i], ExtFILE);
1913 if( g.argc>3 ){
1914 fossil_print("<!------ %h ------->\n", g.argv[i]);
1915 }
1916 }else{
1917 blob_init(&in, g.argv[i], -1);
1918 if( g.argc>3 ){
1919 fossil_print("<!------ script #%d ------->\n", i-1);
1920 }
1921 }
1922 markdown_to_html(&in, 0, &out);
1923 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
1924 safe_html(&out);
1925 blob_write_to_file(&out, "-");
1926
+14 -5
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
18881888
}
18891889
18901890
/*
18911891
** COMMAND: test-markdown-render
18921892
**
1893
-** Usage: %fossil test-markdown-render FILE ...
1893
+** Usage: %fossil test-markdown-render TEXT ...
18941894
**
1895
-** Render markdown in FILE as HTML on stdout.
1895
+** Render markdown in TEXT as HTML on stdout, where TEXT
1896
+** may be a file name or a markdown-formatted string.
1897
+**
18961898
** Options:
18971899
**
18981900
** --safe Restrict the output to use only "safe" HTML
18991901
*/
19001902
void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
19041906
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
19051907
bSafe = find_option("safe",0,0)!=0;
19061908
verify_all_options();
19071909
for(i=2; i<g.argc; i++){
19081910
blob_zero(&out);
1909
- blob_read_from_file(&in, g.argv[i], ExtFILE);
1910
- if( g.argc>3 ){
1911
- fossil_print("<!------ %h ------->\n", g.argv[i]);
1911
+ if(file_isfile(g.argv[i], ExtFILE)){
1912
+ blob_read_from_file(&in, g.argv[i], ExtFILE);
1913
+ if( g.argc>3 ){
1914
+ fossil_print("<!------ %h ------->\n", g.argv[i]);
1915
+ }
1916
+ }else{
1917
+ blob_init(&in, g.argv[i], -1);
1918
+ if( g.argc>3 ){
1919
+ fossil_print("<!------ script #%d ------->\n", i-1);
1920
+ }
19121921
}
19131922
markdown_to_html(&in, 0, &out);
19141923
safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
19151924
safe_html(&out);
19161925
blob_write_to_file(&out, "-");
19171926
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
1888 }
1889
1890 /*
1891 ** COMMAND: test-markdown-render
1892 **
1893 ** Usage: %fossil test-markdown-render FILE ...
1894 **
1895 ** Render markdown in FILE as HTML on stdout.
 
 
1896 ** Options:
1897 **
1898 ** --safe Restrict the output to use only "safe" HTML
1899 */
1900 void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
1904 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1905 bSafe = find_option("safe",0,0)!=0;
1906 verify_all_options();
1907 for(i=2; i<g.argc; i++){
1908 blob_zero(&out);
1909 blob_read_from_file(&in, g.argv[i], ExtFILE);
1910 if( g.argc>3 ){
1911 fossil_print("<!------ %h ------->\n", g.argv[i]);
 
 
 
 
 
 
 
1912 }
1913 markdown_to_html(&in, 0, &out);
1914 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
1915 safe_html(&out);
1916 blob_write_to_file(&out, "-");
1917
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1888,13 +1888,15 @@
1888 }
1889
1890 /*
1891 ** COMMAND: test-markdown-render
1892 **
1893 ** Usage: %fossil test-markdown-render TEXT ...
1894 **
1895 ** Render markdown in TEXT as HTML on stdout, where TEXT
1896 ** may be a file name or a markdown-formatted string.
1897 **
1898 ** Options:
1899 **
1900 ** --safe Restrict the output to use only "safe" HTML
1901 */
1902 void test_markdown_render(void){
@@ -1904,13 +1906,20 @@
1906 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1907 bSafe = find_option("safe",0,0)!=0;
1908 verify_all_options();
1909 for(i=2; i<g.argc; i++){
1910 blob_zero(&out);
1911 if(file_isfile(g.argv[i], ExtFILE)){
1912 blob_read_from_file(&in, g.argv[i], ExtFILE);
1913 if( g.argc>3 ){
1914 fossil_print("<!------ %h ------->\n", g.argv[i]);
1915 }
1916 }else{
1917 blob_init(&in, g.argv[i], -1);
1918 if( g.argc>3 ){
1919 fossil_print("<!------ script #%d ------->\n", i-1);
1920 }
1921 }
1922 markdown_to_html(&in, 0, &out);
1923 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
1924 safe_html(&out);
1925 blob_write_to_file(&out, "-");
1926
+22 -26
--- src/xfer.c
+++ src/xfer.c
@@ -1868,10 +1868,11 @@
18681868
const char *zPCode = db_get("project-code", 0);
18691869
int nErr = 0; /* Number of errors */
18701870
int nRoundtrip= 0; /* Number of HTTP requests */
18711871
int nArtifactSent = 0; /* Total artifacts sent */
18721872
int nArtifactRcvd = 0; /* Total artifacts received */
1873
+ int nPriorArtifact = 0; /* Artifacts received on prior round-trips */
18731874
const char *zOpType = 0;/* Push, Pull, Sync, Clone */
18741875
double rSkew = 0.0; /* Maximum time skew */
18751876
int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
18761877
int uvDoPush = 0; /* Generate uvfile messages to send to server */
18771878
int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
@@ -2196,10 +2197,11 @@
21962197
nCardSent++;
21972198
}
21982199
go = 0;
21992200
nUvGimmeSent = 0;
22002201
nUvFileRcvd = 0;
2202
+ nPriorArtifact = nArtifactRcvd;
22012203
22022204
/* Process the reply that came back from the server */
22032205
while( blob_line(&recv, &xfer.line) ){
22042206
if( blob_buffer(&xfer.line)[0]=='#' ){
22052207
const char *zLine = blob_buffer(&xfer.line);
@@ -2628,48 +2630,42 @@
26282630
}
26292631
nUncRcvd += blob_size(&recv);
26302632
blob_reset(&recv);
26312633
nCycle++;
26322634
2633
- /* If we received one or more files on the previous exchange but
2634
- ** there are still phantoms, then go another round.
2635
- */
2635
+ /* Set go to 1 if we need to continue the sync/push/pull/clone for
2636
+ ** another round. Set go to 0 if it is time to quit. */
26362637
nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
26372638
if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
26382639
go = 1;
26392640
mxPhantomReq = nFileRecv*2;
26402641
if( mxPhantomReq<200 ) mxPhantomReq = 200;
26412642
}else if( (syncFlags & SYNC_CLONE)!=0 && nFileRecv>0 ){
26422643
go = 1;
2644
+ }else if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2645
+ /* Go another round if files are queued to send */
2646
+ go = 1;
2647
+ }else if( xfer.nPrivIGot>0 && nCycle==1 ){
2648
+ go = 1;
2649
+ }else if( (syncFlags & SYNC_CLONE)!=0 ){
2650
+ if( nCycle==1 ){
2651
+ go = 1; /* go at least two rounds on a clone */
2652
+ }else if( cloneSeqno>0 && nArtifactRcvd>nPriorArtifact ){
2653
+ /* Continue the clone until we see the clone_seqno 0" card or
2654
+ ** until we stop receiving artifacts */
2655
+ go = 1;
2656
+ }
2657
+ }else if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ){
2658
+ /* Continue looping as long as new uvfile cards are being received
2659
+ ** and uvgimme cards are being sent. */
2660
+ go = 1;
26432661
}
2662
+
26442663
nCardRcvd = 0;
26452664
xfer.nFileRcvd = 0;
26462665
xfer.nDeltaRcvd = 0;
26472666
xfer.nDanglingFile = 0;
2648
-
2649
- /* If we have one or more files queued to send, then go
2650
- ** another round
2651
- */
2652
- if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2653
- go = 1;
2654
- }
2655
- if( xfer.nPrivIGot>0 && nCycle==1 ) go = 1;
2656
-
2657
- /* If this is a clone, the go at least two rounds */
2658
- if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
2659
-
2660
- /* Stop the cycle if the server sends a "clone_seqno 0" card and
2661
- ** we have gone at least two rounds. Always go at least two rounds
2662
- ** on a clone in order to be sure to retrieve the configuration
2663
- ** information which is only sent on the second round.
2664
- */
2665
- if( cloneSeqno<=0 && nCycle>1 ) go = 0;
2666
-
2667
- /* Continue looping as long as new uvfile cards are being received
2668
- ** and uvgimme cards are being sent. */
2669
- if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ) go = 1;
2670
-
26712667
db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
26722668
if( go ){
26732669
manifest_crosslink_end(MC_PERMIT_HOOKS);
26742670
}else{
26752671
manifest_crosslink_end(MC_PERMIT_HOOKS);
26762672
--- src/xfer.c
+++ src/xfer.c
@@ -1868,10 +1868,11 @@
1868 const char *zPCode = db_get("project-code", 0);
1869 int nErr = 0; /* Number of errors */
1870 int nRoundtrip= 0; /* Number of HTTP requests */
1871 int nArtifactSent = 0; /* Total artifacts sent */
1872 int nArtifactRcvd = 0; /* Total artifacts received */
 
1873 const char *zOpType = 0;/* Push, Pull, Sync, Clone */
1874 double rSkew = 0.0; /* Maximum time skew */
1875 int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
1876 int uvDoPush = 0; /* Generate uvfile messages to send to server */
1877 int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
@@ -2196,10 +2197,11 @@
2196 nCardSent++;
2197 }
2198 go = 0;
2199 nUvGimmeSent = 0;
2200 nUvFileRcvd = 0;
 
2201
2202 /* Process the reply that came back from the server */
2203 while( blob_line(&recv, &xfer.line) ){
2204 if( blob_buffer(&xfer.line)[0]=='#' ){
2205 const char *zLine = blob_buffer(&xfer.line);
@@ -2628,48 +2630,42 @@
2628 }
2629 nUncRcvd += blob_size(&recv);
2630 blob_reset(&recv);
2631 nCycle++;
2632
2633 /* If we received one or more files on the previous exchange but
2634 ** there are still phantoms, then go another round.
2635 */
2636 nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
2637 if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
2638 go = 1;
2639 mxPhantomReq = nFileRecv*2;
2640 if( mxPhantomReq<200 ) mxPhantomReq = 200;
2641 }else if( (syncFlags & SYNC_CLONE)!=0 && nFileRecv>0 ){
2642 go = 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2643 }
 
2644 nCardRcvd = 0;
2645 xfer.nFileRcvd = 0;
2646 xfer.nDeltaRcvd = 0;
2647 xfer.nDanglingFile = 0;
2648
2649 /* If we have one or more files queued to send, then go
2650 ** another round
2651 */
2652 if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2653 go = 1;
2654 }
2655 if( xfer.nPrivIGot>0 && nCycle==1 ) go = 1;
2656
2657 /* If this is a clone, the go at least two rounds */
2658 if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
2659
2660 /* Stop the cycle if the server sends a "clone_seqno 0" card and
2661 ** we have gone at least two rounds. Always go at least two rounds
2662 ** on a clone in order to be sure to retrieve the configuration
2663 ** information which is only sent on the second round.
2664 */
2665 if( cloneSeqno<=0 && nCycle>1 ) go = 0;
2666
2667 /* Continue looping as long as new uvfile cards are being received
2668 ** and uvgimme cards are being sent. */
2669 if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ) go = 1;
2670
2671 db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
2672 if( go ){
2673 manifest_crosslink_end(MC_PERMIT_HOOKS);
2674 }else{
2675 manifest_crosslink_end(MC_PERMIT_HOOKS);
2676
--- src/xfer.c
+++ src/xfer.c
@@ -1868,10 +1868,11 @@
1868 const char *zPCode = db_get("project-code", 0);
1869 int nErr = 0; /* Number of errors */
1870 int nRoundtrip= 0; /* Number of HTTP requests */
1871 int nArtifactSent = 0; /* Total artifacts sent */
1872 int nArtifactRcvd = 0; /* Total artifacts received */
1873 int nPriorArtifact = 0; /* Artifacts received on prior round-trips */
1874 const char *zOpType = 0;/* Push, Pull, Sync, Clone */
1875 double rSkew = 0.0; /* Maximum time skew */
1876 int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
1877 int uvDoPush = 0; /* Generate uvfile messages to send to server */
1878 int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
@@ -2196,10 +2197,11 @@
2197 nCardSent++;
2198 }
2199 go = 0;
2200 nUvGimmeSent = 0;
2201 nUvFileRcvd = 0;
2202 nPriorArtifact = nArtifactRcvd;
2203
2204 /* Process the reply that came back from the server */
2205 while( blob_line(&recv, &xfer.line) ){
2206 if( blob_buffer(&xfer.line)[0]=='#' ){
2207 const char *zLine = blob_buffer(&xfer.line);
@@ -2628,48 +2630,42 @@
2630 }
2631 nUncRcvd += blob_size(&recv);
2632 blob_reset(&recv);
2633 nCycle++;
2634
2635 /* Set go to 1 if we need to continue the sync/push/pull/clone for
2636 ** another round. Set go to 0 if it is time to quit. */
 
2637 nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
2638 if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
2639 go = 1;
2640 mxPhantomReq = nFileRecv*2;
2641 if( mxPhantomReq<200 ) mxPhantomReq = 200;
2642 }else if( (syncFlags & SYNC_CLONE)!=0 && nFileRecv>0 ){
2643 go = 1;
2644 }else if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
2645 /* Go another round if files are queued to send */
2646 go = 1;
2647 }else if( xfer.nPrivIGot>0 && nCycle==1 ){
2648 go = 1;
2649 }else if( (syncFlags & SYNC_CLONE)!=0 ){
2650 if( nCycle==1 ){
2651 go = 1; /* go at least two rounds on a clone */
2652 }else if( cloneSeqno>0 && nArtifactRcvd>nPriorArtifact ){
2653 /* Continue the clone until we see the clone_seqno 0" card or
2654 ** until we stop receiving artifacts */
2655 go = 1;
2656 }
2657 }else if( nUvGimmeSent>0 && (nUvFileRcvd>0 || nCycle<3) ){
2658 /* Continue looping as long as new uvfile cards are being received
2659 ** and uvgimme cards are being sent. */
2660 go = 1;
2661 }
2662
2663 nCardRcvd = 0;
2664 xfer.nFileRcvd = 0;
2665 xfer.nDeltaRcvd = 0;
2666 xfer.nDanglingFile = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2667 db_multi_exec("DROP TABLE onremote; DROP TABLE unk;");
2668 if( go ){
2669 manifest_crosslink_end(MC_PERMIT_HOOKS);
2670 }else{
2671 manifest_crosslink_end(MC_PERMIT_HOOKS);
2672
+25
--- www/chat.md
+++ www/chat.md
@@ -96,10 +96,35 @@
9696
By default, the list of new-message notification sounds is limited to
9797
a few built in to the fossil binary. In addition, any
9898
[unversioned files](./unvers.wiki) named `alert-sounds/*.{mp3,wav,ogg}`
9999
will be included in that list. To switch sounds, tap the "settings"
100100
button.
101
+
102
+### <a id='connection'></a> Who's Online?
103
+
104
+Because the chat app has to be able to work over transient CGI-based
105
+connections, as opposed to a stable socket connection to the server,
106
+real-time tracking of "who's online" is not feasible. As of version
107
+2.17, chat offers an optional feature, toggleable in the settings,
108
+which can list users who have posted messages in the client's current
109
+list of loaded messages. This is not the same thing as tracking who's
110
+online, but it gives an overview of which users have been active most
111
+recently, noting that "lurkers" (people who post no messages) will not
112
+show up in that list, nor does the chat infrastructure have a way to
113
+track and present those. That list can be used to filter messages on a
114
+specific user by tapping on that user's name, tapping a second time to
115
+remove the filter.
116
+
117
+Sidebar: message deletion is a type of message and deletions count
118
+towards updates in the recent activity list (counted for the person
119
+who performed the deletion, not the author of the deleted
120
+comment). That can potentially lead to odd corner cases where a user
121
+shows up in the list but has no messages which are currently visible
122
+because they were deleted, or an admin user who has not posted
123
+anything but deleted a message. That is a known minor cosmetic-only
124
+bug with a resolution of "will not fix."
125
+
101126
102127
## Implementation Details
103128
104129
*You do not need to understand how Fossil chat works in order to use it.
105130
But many developers prefer to know how their tools work.
106131
--- www/chat.md
+++ www/chat.md
@@ -96,10 +96,35 @@
96 By default, the list of new-message notification sounds is limited to
97 a few built in to the fossil binary. In addition, any
98 [unversioned files](./unvers.wiki) named `alert-sounds/*.{mp3,wav,ogg}`
99 will be included in that list. To switch sounds, tap the "settings"
100 button.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
102 ## Implementation Details
103
104 *You do not need to understand how Fossil chat works in order to use it.
105 But many developers prefer to know how their tools work.
106
--- www/chat.md
+++ www/chat.md
@@ -96,10 +96,35 @@
96 By default, the list of new-message notification sounds is limited to
97 a few built in to the fossil binary. In addition, any
98 [unversioned files](./unvers.wiki) named `alert-sounds/*.{mp3,wav,ogg}`
99 will be included in that list. To switch sounds, tap the "settings"
100 button.
101
102 ### <a id='connection'></a> Who's Online?
103
104 Because the chat app has to be able to work over transient CGI-based
105 connections, as opposed to a stable socket connection to the server,
106 real-time tracking of "who's online" is not feasible. As of version
107 2.17, chat offers an optional feature, toggleable in the settings,
108 which can list users who have posted messages in the client's current
109 list of loaded messages. This is not the same thing as tracking who's
110 online, but it gives an overview of which users have been active most
111 recently, noting that "lurkers" (people who post no messages) will not
112 show up in that list, nor does the chat infrastructure have a way to
113 track and present those. That list can be used to filter messages on a
114 specific user by tapping on that user's name, tapping a second time to
115 remove the filter.
116
117 Sidebar: message deletion is a type of message and deletions count
118 towards updates in the recent activity list (counted for the person
119 who performed the deletion, not the author of the deleted
120 comment). That can potentially lead to odd corner cases where a user
121 shows up in the list but has no messages which are currently visible
122 because they were deleted, or an admin user who has not posted
123 anything but deleted a message. That is a known minor cosmetic-only
124 bug with a resolution of "will not fix."
125
126
127 ## Implementation Details
128
129 *You do not need to understand how Fossil chat works in order to use it.
130 But many developers prefer to know how their tools work.
131

Keyboard Shortcuts

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