summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.c18
-rw-r--r--src/gmdocument.c69
-rw-r--r--src/gmdocument.h15
-rw-r--r--src/history.c82
-rw-r--r--src/history.h12
-rw-r--r--src/macos.m2
-rw-r--r--src/media.c5
-rw-r--r--src/media.h2
-rw-r--r--src/ui/color.c6
-rw-r--r--src/ui/color.h40
-rw-r--r--src/ui/documentwidget.c52
-rw-r--r--src/ui/inputwidget.c4
-rw-r--r--src/ui/inputwidget.h25
-rw-r--r--src/ui/labelwidget.c2
-rw-r--r--src/ui/text.c4
15 files changed, 250 insertions, 88 deletions
diff --git a/src/app.c b/src/app.c
index 9697ee2b..cc172139 100644
--- a/src/app.c
+++ b/src/app.c
@@ -889,13 +889,25 @@ const iString *debugInfo_App(void) {
889 extern char **environ; /* The environment variables. */ 889 extern char **environ; /* The environment variables. */
890 iApp *d = &app_; 890 iApp *d = &app_;
891 iString *msg = collectNew_String(); 891 iString *msg = collectNew_String();
892 iObjectList *docs = iClob(listDocuments_App(NULL));
892 format_String(msg, "# Debug information\n"); 893 format_String(msg, "# Debug information\n");
894 appendFormat_String(msg, "## Memory usage\n"); {
895 iMemInfo total = { 0, 0 };
896 iForEach(ObjectList, i, docs) {
897 iDocumentWidget *doc = i.object;
898 iMemInfo usage = memoryUsage_History(history_DocumentWidget(doc));
899 total.cacheSize += usage.cacheSize;
900 total.memorySize += usage.memorySize;
901 }
902 appendFormat_String(msg, "Total cache: %.3f MB\n", total.cacheSize / 1.0e6f);
903 appendFormat_String(msg, "Total memory: %.3f MB\n", total.memorySize / 1.0e6f);
904 }
893 appendFormat_String(msg, "## Documents\n"); 905 appendFormat_String(msg, "## Documents\n");
894 iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) { 906 iForEach(ObjectList, k, docs) {
895 iDocumentWidget *doc = k.object; 907 iDocumentWidget *doc = k.object;
896 appendFormat_String(msg, "### Tab %d.%zu: %s\n", 908 appendFormat_String(msg, "### Tab %d.%zu: %s\n",
897 constAs_Widget(doc)->root == get_Window()->roots[0] ? 0 : 1, 909 constAs_Widget(doc)->root == get_Window()->roots[0] ? 1 : 2,
898 childIndex_Widget(constAs_Widget(doc)->parent, k.object), 910 childIndex_Widget(constAs_Widget(doc)->parent, k.object) + 1,
899 cstr_String(bookmarkTitle_DocumentWidget(doc))); 911 cstr_String(bookmarkTitle_DocumentWidget(doc)));
900 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc)))); 912 append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc))));
901 } 913 }
diff --git a/src/gmdocument.c b/src/gmdocument.c
index b1c3f2ef..67adb9cc 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -47,7 +47,7 @@ iBool isDark_GmDocumentTheme(enum iGmDocumentTheme d) {
47iDeclareType(GmLink) 47iDeclareType(GmLink)
48 48
49struct Impl_GmLink { 49struct Impl_GmLink {
50 iString url; 50 iString url; /* resolved */
51 iRangecc urlRange; /* URL in the source */ 51 iRangecc urlRange; /* URL in the source */
52 iRangecc labelRange; /* label in the source */ 52 iRangecc labelRange; /* label in the source */
53 iRangecc labelIcon; /* special icon defined in the label text */ 53 iRangecc labelIcon; /* special icon defined in the label text */
@@ -74,8 +74,9 @@ iDefineTypeConstruction(GmLink)
74struct Impl_GmDocument { 74struct Impl_GmDocument {
75 iObject object; 75 iObject object;
76 enum iGmDocumentFormat format; 76 enum iGmDocumentFormat format;
77 iString source; 77 iString unormSource; /* unnormalized source */
78 iString url; /* for resolving relative links */ 78 iString source; /* normalized source */
79 iString url; /* for resolving relative links */
79 iString localHost; 80 iString localHost;
80 iInt2 size; 81 iInt2 size;
81 iArray layout; /* contents of source, laid out in document space */ 82 iArray layout; /* contents of source, laid out in document space */
@@ -437,7 +438,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
437 isFirstText = iFalse; 438 isFirstText = iFalse;
438 } 439 }
439 while (nextSplit_Rangecc(content, "\n", &contentLine)) { 440 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
440 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ 441 iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */
442 if (*line.end == '\r') {
443 line.end--; /* trim CR always */
444 }
441 iGmRun run = { .color = white_ColorId }; 445 iGmRun run = { .color = white_ColorId };
442 enum iGmLineType type; 446 enum iGmLineType type;
443 float indent = 0.0f; 447 float indent = 0.0f;
@@ -857,10 +861,13 @@ static void doLayout_GmDocument_(iGmDocument *d) {
857 } 861 }
858 } 862 }
859 } 863 }
864 printf("[GmDocument] layout size: %zu runs (%zu bytes)\n",
865 size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun));
860} 866}
861 867
862void init_GmDocument(iGmDocument *d) { 868void init_GmDocument(iGmDocument *d) {
863 d->format = gemini_GmDocumentFormat; 869 d->format = gemini_GmDocumentFormat;
870 init_String(&d->unormSource);
864 init_String(&d->source); 871 init_String(&d->source);
865 init_String(&d->url); 872 init_String(&d->url);
866 init_String(&d->localHost); 873 init_String(&d->localHost);
@@ -891,6 +898,7 @@ void deinit_GmDocument(iGmDocument *d) {
891 deinit_String(&d->localHost); 898 deinit_String(&d->localHost);
892 deinit_String(&d->url); 899 deinit_String(&d->url);
893 deinit_String(&d->source); 900 deinit_String(&d->source);
901 deinit_String(&d->unormSource);
894} 902}
895 903
896iMedia *media_GmDocument(iGmDocument *d) { 904iMedia *media_GmDocument(iGmDocument *d) {
@@ -901,6 +909,7 @@ const iMedia *constMedia_GmDocument(const iGmDocument *d) {
901 return d->media; 909 return d->media;
902} 910}
903 911
912#if 0
904void reset_GmDocument(iGmDocument *d) { 913void reset_GmDocument(iGmDocument *d) {
905 clear_Media(d->media); 914 clear_Media(d->media);
906 clearLinks_GmDocument_(d); 915 clearLinks_GmDocument_(d);
@@ -909,8 +918,11 @@ void reset_GmDocument(iGmDocument *d) {
909 clear_Array(&d->preMeta); 918 clear_Array(&d->preMeta);
910 clear_String(&d->url); 919 clear_String(&d->url);
911 clear_String(&d->localHost); 920 clear_String(&d->localHost);
921 clear_String(&d->source);
922 clear_String(&d->unormSource);
912 d->themeSeed = 0; 923 d->themeSeed = 0;
913} 924}
925#endif
914 926
915static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { 927static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {
916 set_Color(tmQuoteIcon_ColorId, 928 set_Color(tmQuoteIcon_ColorId,
@@ -1431,6 +1443,8 @@ static void normalize_GmDocument(iGmDocument *d) {
1431 isPreformat = iTrue; /* Cannot be turned off. */ 1443 isPreformat = iTrue; /* Cannot be turned off. */
1432 } 1444 }
1433 const int preTabWidth = 4; /* TODO: user-configurable parameter */ 1445 const int preTabWidth = 4; /* TODO: user-configurable parameter */
1446 iBool wasNormalized = iFalse;
1447 iBool hasTabs = iFalse;
1434 while (nextSplit_Rangecc(src, "\n", &line)) { 1448 while (nextSplit_Rangecc(src, "\n", &line)) {
1435 if (isPreformat) { 1449 if (isPreformat) {
1436 /* Replace any tab characters with spaces for visualization. */ 1450 /* Replace any tab characters with spaces for visualization. */
@@ -1441,10 +1455,16 @@ static void normalize_GmDocument(iGmDocument *d) {
1441 while (numSpaces-- > 0) { 1455 while (numSpaces-- > 0) {
1442 appendCStrN_String(normalized, " ", 1); 1456 appendCStrN_String(normalized, " ", 1);
1443 } 1457 }
1458 hasTabs = iTrue;
1459 wasNormalized = iTrue;
1444 } 1460 }
1445 else if (*ch != '\r') { 1461 else if (*ch != '\v') {
1446 appendCStrN_String(normalized, ch, 1); 1462 appendCStrN_String(normalized, ch, 1);
1447 } 1463 }
1464 else {
1465 hasTabs = iTrue;
1466 wasNormalized = iTrue;
1467 }
1448 } 1468 }
1449 appendCStr_String(normalized, "\n"); 1469 appendCStr_String(normalized, "\n");
1450 if (d->format == gemini_GmDocumentFormat && 1470 if (d->format == gemini_GmDocumentFormat &&
@@ -1463,7 +1483,10 @@ static void normalize_GmDocument(iGmDocument *d) {
1463 int spaceCount = 0; 1483 int spaceCount = 0;
1464 for (const char *ch = line.start; ch != line.end; ch++) { 1484 for (const char *ch = line.start; ch != line.end; ch++) {
1465 char c = *ch; 1485 char c = *ch;
1466 if (c == '\r') continue; 1486 if (c == '\v') {
1487 wasNormalized = iTrue;
1488 continue;
1489 }
1467 if (isNormalizableSpace_(c)) { 1490 if (isNormalizableSpace_(c)) {
1468 if (isPrevSpace) { 1491 if (isPrevSpace) {
1469 if (++spaceCount == 8) { 1492 if (++spaceCount == 8) {
@@ -1472,9 +1495,13 @@ static void normalize_GmDocument(iGmDocument *d) {
1472 popBack_Block(&normalized->chars); 1495 popBack_Block(&normalized->chars);
1473 pushBack_Block(&normalized->chars, '\t'); 1496 pushBack_Block(&normalized->chars, '\t');
1474 } 1497 }
1498 wasNormalized = iTrue;
1475 continue; /* skip repeated spaces */ 1499 continue; /* skip repeated spaces */
1476 } 1500 }
1477 c = ' '; 1501 if (c != ' ') {
1502 c = ' ';
1503 wasNormalized = iTrue;
1504 }
1478 isPrevSpace = iTrue; 1505 isPrevSpace = iTrue;
1479 } 1506 }
1480 else { 1507 else {
@@ -1485,7 +1512,13 @@ static void normalize_GmDocument(iGmDocument *d) {
1485 } 1512 }
1486 appendCStr_String(normalized, "\n"); 1513 appendCStr_String(normalized, "\n");
1487 } 1514 }
1515 printf("hasTabs: %d\n", hasTabs);
1516 printf("wasNormalized: %d\n", wasNormalized);
1517 fflush(stdout);
1488 set_String(&d->source, collect_String(normalized)); 1518 set_String(&d->source, collect_String(normalized));
1519 printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source));
1520 /* normalized source has an extra newline at the end */
1521// iAssert(wasNormalized || equal_String(&d->unormSource, &d->source));
1489} 1522}
1490 1523
1491void setUrl_GmDocument(iGmDocument *d, const iString *url) { 1524void setUrl_GmDocument(iGmDocument *d, const iString *url) {
@@ -1496,8 +1529,18 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) {
1496 updateIconBasedOnUrl_GmDocument_(d); 1529 updateIconBasedOnUrl_GmDocument_(d);
1497} 1530}
1498 1531
1499void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 1532void setSource_GmDocument(iGmDocument *d, const iString *source, int width,
1500 set_String(&d->source, source); 1533 enum iGmDocumentUpdate updateType) {
1534 printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n",
1535 size_String(source), width, updateType == final_GmDocumentUpdate);
1536 if (size_String(source) == size_String(&d->unormSource)) {
1537 iAssert(equal_String(source, &d->unormSource));
1538 printf("[GmDocument] source is unchanged!\n");
1539 return; /* Nothing to do. */
1540 }
1541 set_String(&d->unormSource, source);
1542 /* Normalize. */
1543 set_String(&d->source, &d->unormSource);
1501 if (isNormalized_GmDocument_(d)) { 1544 if (isNormalized_GmDocument_(d)) {
1502 normalize_GmDocument(d); 1545 normalize_GmDocument(d);
1503 } 1546 }
@@ -1599,6 +1642,14 @@ const iString *source_GmDocument(const iGmDocument *d) {
1599 return &d->source; 1642 return &d->source;
1600} 1643}
1601 1644
1645size_t memorySize_GmDocument(const iGmDocument *d) {
1646 return size_String(&d->unormSource) +
1647 size_String(&d->source) +
1648 size_Array(&d->layout) * sizeof(iGmRun) +
1649 size_Array(&d->links) * sizeof(iGmLink) +
1650 memorySize_Media(d->media);
1651}
1652
1602iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { 1653iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
1603 const char * src = constBegin_String(&d->source); 1654 const char * src = constBegin_String(&d->source);
1604 const size_t startPos = (start ? start - src : 0); 1655 const size_t startPos = (start ? start - src : 0);
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 574f0acf..1e54a16a 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -125,12 +125,12 @@ enum iGmRunMediaType {
125 125
126struct Impl_GmRun { 126struct Impl_GmRun {
127 iRangecc text; 127 iRangecc text;
128 iRect bounds; /* used for hit testing, may extend to edges */
129 iRect visBounds; /* actual visual bounds */
128 uint8_t font; 130 uint8_t font;
129 uint8_t color; 131 uint8_t color;
130 uint8_t flags; 132 uint8_t flags;
131 uint8_t mediaType; 133 uint8_t mediaType;
132 iRect bounds; /* used for hit testing, may extend to edges */
133 iRect visBounds; /* actual visual bounds */
134 uint16_t preId; /* preformatted block ID (sequential) */ 134 uint16_t preId; /* preformatted block ID (sequential) */
135 iGmLinkId linkId; /* zero for non-links */ 135 iGmLinkId linkId; /* zero for non-links */
136 uint16_t mediaId; /* zero if not an image */ 136 uint16_t mediaId; /* zero if not an image */
@@ -160,6 +160,11 @@ enum iGmDocumentBanner {
160 certificateWarning_GmDocumentBanner, 160 certificateWarning_GmDocumentBanner,
161}; 161};
162 162
163enum iGmDocumentUpdate {
164 partial_GmDocumentUpdate, /* appending more content */
165 final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */
166};
167
163void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 168void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
164void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); 169void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format);
165void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); 170void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type);
@@ -167,10 +172,11 @@ void setWidth_GmDocument (iGmDocument *, int width);
167void redoLayout_GmDocument (iGmDocument *); 172void redoLayout_GmDocument (iGmDocument *);
168iBool updateOpenURLs_GmDocument(iGmDocument *); 173iBool updateOpenURLs_GmDocument(iGmDocument *);
169void setUrl_GmDocument (iGmDocument *, const iString *url); 174void setUrl_GmDocument (iGmDocument *, const iString *url);
170void setSource_GmDocument (iGmDocument *, const iString *source, int width); 175void setSource_GmDocument (iGmDocument *, const iString *source, int width,
176 enum iGmDocumentUpdate updateType);
171void foldPre_GmDocument (iGmDocument *, uint16_t preId); 177void foldPre_GmDocument (iGmDocument *, uint16_t preId);
172 178
173void reset_GmDocument (iGmDocument *); /* free images */ 179//void reset_GmDocument (iGmDocument *); /* free images */
174 180
175typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 181typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
176 182
@@ -190,6 +196,7 @@ enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *);
190const iString * bannerText_GmDocument (const iGmDocument *); 196const iString * bannerText_GmDocument (const iGmDocument *);
191const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 197const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
192const iString * source_GmDocument (const iGmDocument *); 198const iString * source_GmDocument (const iGmDocument *);
199size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
193 200
194iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); 201iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
195iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 202iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
diff --git a/src/history.c b/src/history.c
index 9f4e415b..87cf28e6 100644
--- a/src/history.c
+++ b/src/history.c
@@ -34,11 +34,13 @@ static const size_t maxStack_History_ = 50; /* back/forward navigable items */
34 34
35void init_RecentUrl(iRecentUrl *d) { 35void init_RecentUrl(iRecentUrl *d) {
36 init_String(&d->url); 36 init_String(&d->url);
37 d->normScrollY = 0; 37 d->normScrollY = 0;
38 d->cachedResponse = NULL; 38 d->cachedResponse = NULL;
39 d->cachedDoc = NULL;
39} 40}
40 41
41void deinit_RecentUrl(iRecentUrl *d) { 42void deinit_RecentUrl(iRecentUrl *d) {
43 iRelease(d->cachedDoc);
42 deinit_String(&d->url); 44 deinit_String(&d->url);
43 delete_GmResponse(d->cachedResponse); 45 delete_GmResponse(d->cachedResponse);
44} 46}
@@ -48,11 +50,29 @@ iDefineTypeConstruction(RecentUrl)
48iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { 50iRecentUrl *copy_RecentUrl(const iRecentUrl *d) {
49 iRecentUrl *copy = new_RecentUrl(); 51 iRecentUrl *copy = new_RecentUrl();
50 set_String(&copy->url, &d->url); 52 set_String(&copy->url, &d->url);
51 copy->normScrollY = d->normScrollY; 53 copy->normScrollY = d->normScrollY;
52 copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL; 54 copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL;
55 copy->cachedDoc = ref_Object(d->cachedDoc);
53 return copy; 56 return copy;
54} 57}
55 58
59size_t cacheSize_RecentUrl(const iRecentUrl *d) {
60 size_t size = 0;
61 if (d->cachedResponse) {
62 size += size_String(&d->cachedResponse->meta);
63 size += size_Block(&d->cachedResponse->body);
64 }
65 return size;
66}
67
68size_t memorySize_RecentUrl(const iRecentUrl *d) {
69 size_t size = cacheSize_RecentUrl(d);
70 if (d->cachedDoc) {
71 size += memorySize_GmDocument(d->cachedDoc);
72 }
73 return size;
74}
75
56/*----------------------------------------------------------------------------------------------*/ 76/*----------------------------------------------------------------------------------------------*/
57 77
58struct Impl_History { 78struct Impl_History {
@@ -88,24 +108,45 @@ iHistory *copy_History(const iHistory *d) {
88 return copy; 108 return copy;
89} 109}
90 110
111iMemInfo memoryUsage_History(const iHistory *d) {
112 iMemInfo mem = { 0, 0 };
113 iConstForEach(Array, i, &d->recent) {
114 const iRecentUrl *item = i.value;
115 mem.cacheSize += cacheSize_RecentUrl(item);
116 mem.memorySize += memorySize_RecentUrl(item);
117 }
118 return mem;
119}
120
91iString *debugInfo_History(const iHistory *d) { 121iString *debugInfo_History(const iHistory *d) {
92 iString *str = new_String(); 122 iString *str = new_String();
93 format_String(str, 123 format_String(str,
94 "```\n" 124 "```\n"
95 "Idx | Size | SP%% | URL\n" 125 "Idx | Cache | Memory | SP%% | URL\n"
96 "----+---------+-----+-----\n"); 126 "----+---------+----------+-----+-----\n");
97 size_t totalSize = 0; 127 size_t totalCache = 0;
128 size_t totalMemory = 0;
98 iConstForEach(Array, i, &d->recent) { 129 iConstForEach(Array, i, &d->recent) {
99 const iRecentUrl *item = i.value; 130 const iRecentUrl *item = i.value;
100 appendFormat_String( 131 appendFormat_String(
101 str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1); 132 str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1);
102 if (item->cachedResponse) { 133 const size_t cacheSize = cacheSize_RecentUrl(item);
103 appendFormat_String(str, "%7zu", size_Block(&item->cachedResponse->body)); 134 const size_t memSize = memorySize_RecentUrl(item);
104 totalSize += size_Block(&item->cachedResponse->body); 135 if (cacheSize) {
136 appendFormat_String(str, "%7zu", cacheSize);
137 totalCache += cacheSize;
105 } 138 }
106 else { 139 else {
107 appendFormat_String(str, " --"); 140 appendFormat_String(str, " --");
108 } 141 }
142 appendCStr_String(str, " | ");
143 if (memSize) {
144 appendFormat_String(str, "%8zu", memSize);
145 totalMemory += memSize;
146 }
147 else {
148 appendFormat_String(str, " --");
149 }
109 appendFormat_String(str, 150 appendFormat_String(str,
110 " | %3d | %s\n", 151 " | %3d | %s\n",
111 iRound(100.0f * item->normScrollY), 152 iRound(100.0f * item->normScrollY),
@@ -114,8 +155,10 @@ iString *debugInfo_History(const iHistory *d) {
114 appendFormat_String(str, "\n```\n"); 155 appendFormat_String(str, "\n```\n");
115 appendFormat_String(str, 156 appendFormat_String(str,
116 "Total cached data: %.3f MB\n" 157 "Total cached data: %.3f MB\n"
158 "Total memory usage: %.3f MB\n"
117 "Navigation position: %zu\n\n", 159 "Navigation position: %zu\n\n",
118 totalSize / 1.0e6f, 160 totalCache / 1.0e6f,
161 totalMemory / 1.0e6f,
119 d->recentPos); 162 d->recentPos);
120 return str; 163 return str;
121} 164}
@@ -301,14 +344,22 @@ void setCachedResponse_History(iHistory *d, const iGmResponse *response) {
301 unlock_Mutex(d->mtx); 344 unlock_Mutex(d->mtx);
302} 345}
303 346
347void setCachedDocument_History(iHistory *d, iGmDocument *doc) {
348 lock_Mutex(d->mtx);
349 iRecentUrl *item = mostRecentUrl_History(d);
350 if (item && item->cachedDoc != doc) {
351 iRelease(item->cachedDoc);
352 item->cachedDoc = ref_Object(doc);
353 }
354 unlock_Mutex(d->mtx);
355}
356
304size_t cacheSize_History(const iHistory *d) { 357size_t cacheSize_History(const iHistory *d) {
305 size_t cached = 0; 358 size_t cached = 0;
306 lock_Mutex(d->mtx); 359 lock_Mutex(d->mtx);
307 iConstForEach(Array, i, &d->recent) { 360 iConstForEach(Array, i, &d->recent) {
308 const iRecentUrl *url = i.value; 361 const iRecentUrl *url = i.value;
309 if (url->cachedResponse) { 362 cached += cacheSize_RecentUrl(url);
310 cached += size_Block(&url->cachedResponse->body);
311 }
312 } 363 }
313 unlock_Mutex(d->mtx); 364 unlock_Mutex(d->mtx);
314 return cached; 365 return cached;
@@ -335,9 +386,9 @@ size_t pruneLeastImportant_History(iHistory *d) {
335 lock_Mutex(d->mtx); 386 lock_Mutex(d->mtx);
336 iConstForEach(Array, i, &d->recent) { 387 iConstForEach(Array, i, &d->recent) {
337 const iRecentUrl *url = i.value; 388 const iRecentUrl *url = i.value;
338 if (url->cachedResponse) { 389 if (url->cachedResponse || url->cachedDoc) {
339 const double urlScore = 390 const double urlScore =
340 size_Block(&url->cachedResponse->body) * 391 cacheSize_RecentUrl(url) *
341 pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25); 392 pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25);
342 if (urlScore > score) { 393 if (urlScore > score) {
343 chosen = index_ArrayConstIterator(&i); 394 chosen = index_ArrayConstIterator(&i);
@@ -347,9 +398,10 @@ size_t pruneLeastImportant_History(iHistory *d) {
347 } 398 }
348 if (chosen != iInvalidPos) { 399 if (chosen != iInvalidPos) {
349 iRecentUrl *url = at_Array(&d->recent, chosen); 400 iRecentUrl *url = at_Array(&d->recent, chosen);
350 delta = size_Block(&url->cachedResponse->body); 401 delta = cacheSize_RecentUrl(url);
351 delete_GmResponse(url->cachedResponse); 402 delete_GmResponse(url->cachedResponse);
352 url->cachedResponse = NULL; 403 url->cachedResponse = NULL;
404 iReleasePtr(&url->cachedDoc);
353 } 405 }
354 unlock_Mutex(d->mtx); 406 unlock_Mutex(d->mtx);
355 return delta; 407 return delta;
diff --git a/src/history.h b/src/history.h
index 164a61d6..ccc19d27 100644
--- a/src/history.h
+++ b/src/history.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "gmdocument.h"
25#include "gmrequest.h" 26#include "gmrequest.h"
26 27
27#include <the_Foundation/ptrarray.h> 28#include <the_Foundation/ptrarray.h>
@@ -37,6 +38,14 @@ struct Impl_RecentUrl {
37 iString url; 38 iString url;
38 float normScrollY; /* normalized to document height */ 39 float normScrollY; /* normalized to document height */
39 iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ 40 iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */
41 iGmDocument *cachedDoc; /* cached copy of the presentation: layout and media (not serialized) */
42};
43
44iDeclareType(MemInfo)
45
46struct Impl_MemInfo {
47 size_t cacheSize; /* number of bytes stored persistently */
48 size_t memorySize; /* number of bytes stored in RAM */
40}; 49};
41 50
42/*----------------------------------------------------------------------------------------------*/ 51/*----------------------------------------------------------------------------------------------*/
@@ -51,6 +60,7 @@ void clear_History (iHistory *);
51void add_History (iHistory *, const iString *url); 60void add_History (iHistory *, const iString *url);
52void replace_History (iHistory *, const iString *url); 61void replace_History (iHistory *, const iString *url);
53void setCachedResponse_History (iHistory *, const iGmResponse *response); 62void setCachedResponse_History (iHistory *, const iGmResponse *response);
63void setCachedDocument_History (iHistory *, iGmDocument *doc);
54iBool goBack_History (iHistory *); 64iBool goBack_History (iHistory *);
55iBool goForward_History (iHistory *); 65iBool goForward_History (iHistory *);
56iRecentUrl *recentUrl_History (iHistory *, size_t pos); 66iRecentUrl *recentUrl_History (iHistory *, size_t pos);
@@ -75,4 +85,4 @@ const iGmResponse *
75size_t cacheSize_History (const iHistory *); 85size_t cacheSize_History (const iHistory *);
76 86
77iString * debugInfo_History (const iHistory *); 87iString * debugInfo_History (const iHistory *);
78 88iMemInfo memoryUsage_History (const iHistory *);
diff --git a/src/macos.m b/src/macos.m
index 61507b6f..709a97f7 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -479,7 +479,7 @@ void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *
479 [menu setAutoenablesItems:NO]; 479 [menu setAutoenablesItems:NO];
480 for (size_t i = 0; i < count; ++i) { 480 for (size_t i = 0; i < count; ++i) {
481 const char *label = translateCStr_Lang(items[i].label); 481 const char *label = translateCStr_Lang(items[i].label);
482 if (label[0] == '\r') { 482 if (label[0] == '\v') {
483 /* Skip the formatting escape. */ 483 /* Skip the formatting escape. */
484 label += 2; 484 label += 2;
485 } 485 }
diff --git a/src/media.c b/src/media.c
index 1313b7da..2ec2109d 100644
--- a/src/media.c
+++ b/src/media.c
@@ -261,6 +261,11 @@ void clear_Media(iMedia *d) {
261 clear_PtrArray(&d->downloads); 261 clear_PtrArray(&d->downloads);
262} 262}
263 263
264size_t memorySize_Media(const iMedia *d) {
265 /* TODO: Calculate the actual memory use. */
266 return 0;
267}
268
264iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { 269iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) {
265 iGmDownload *dl = NULL; 270 iGmDownload *dl = NULL;
266 iMediaId existing = findLinkDownload_Media(d, linkId); 271 iMediaId existing = findLinkDownload_Media(d, linkId);
diff --git a/src/media.h b/src/media.h
index 7cc941d0..c6973b79 100644
--- a/src/media.h
+++ b/src/media.h
@@ -50,6 +50,8 @@ void clear_Media (iMedia *);
50iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); 50iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url);
51iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); 51iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);
52 52
53size_t memorySize_Media (const iMedia *);
54
53iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); 55iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);
54iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); 56iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out);
55iInt2 imageSize_Media (const iMedia *, iMediaId imageId); 57iInt2 imageSize_Media (const iMedia *, iMediaId imageId);
diff --git a/src/ui/color.c b/src/ui/color.c
index a6ba18e4..6c51bc06 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -468,12 +468,12 @@ const char *escape_Color(int color) {
468 return esc[color]; 468 return esc[color];
469 } 469 }
470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ 470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */
471 /* Double-\r is used for range extension. */ 471 /* Double-\v is used for range extension. */
472 if (color + asciiBase_ColorEscape > 127) { 472 if (color + asciiBase_ColorEscape > 127) {
473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); 473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127);
474 return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); 474 return format_CStr("\v\v%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape);
475 } 475 }
476 return format_CStr("\r%c", color + asciiBase_ColorEscape); 476 return format_CStr("\v%c", color + asciiBase_ColorEscape);
477} 477}
478 478
479iHSLColor setSat_HSLColor(iHSLColor d, float sat) { 479iHSLColor setSat_HSLColor(iHSLColor d, float sat) {
diff --git a/src/ui/color.h b/src/ui/color.h
index d2fa3c00..aafc1794 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -187,26 +187,26 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
187#define asciiBase_ColorEscape 33 187#define asciiBase_ColorEscape 33
188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) 188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
189 189
190#define restore_ColorEscape "\r\x24" /* ASCII Cancel */ 190#define restore_ColorEscape "\v\x24" /* ASCII Cancel */
191#define black_ColorEscape "\r!" 191#define black_ColorEscape "\v!"
192#define gray25_ColorEscape "\r\"" 192#define gray25_ColorEscape "\v\""
193#define gray50_ColorEscape "\r#" 193#define gray50_ColorEscape "\v#"
194#define gray75_ColorEscape "\r$" 194#define gray75_ColorEscape "\v$"
195#define white_ColorEscape "\r%" 195#define white_ColorEscape "\v%"
196#define brown_ColorEscape "\r&" 196#define brown_ColorEscape "\v&"
197#define orange_ColorEscape "\r'" 197#define orange_ColorEscape "\v'"
198#define teal_ColorEscape "\r(" 198#define teal_ColorEscape "\v("
199#define cyan_ColorEscape "\r)" 199#define cyan_ColorEscape "\v)"
200#define yellow_ColorEscape "\r*" 200#define yellow_ColorEscape "\v*"
201#define red_ColorEscape "\r+" 201#define red_ColorEscape "\v+"
202#define magenta_ColorEscape "\r," 202#define magenta_ColorEscape "\v,"
203#define blue_ColorEscape "\r-" 203#define blue_ColorEscape "\v-"
204#define green_ColorEscape "\r." 204#define green_ColorEscape "\v."
205#define uiText_ColorEscape "\r4" 205#define uiText_ColorEscape "\v4"
206#define uiTextAction_ColorEscape "\r<" 206#define uiTextAction_ColorEscape "\v<"
207#define uiTextCaution_ColorEscape "\r=" 207#define uiTextCaution_ColorEscape "\v="
208#define uiTextStrong_ColorEscape "\r:" 208#define uiTextStrong_ColorEscape "\v:"
209#define uiHeading_ColorEscape "\rR" 209#define uiHeading_ColorEscape "\vR"
210 210
211iDeclareType(Color) 211iDeclareType(Color)
212iDeclareType(HSLColor) 212iDeclareType(HSLColor)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 9d60f86f..5dcdace6 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -975,13 +975,11 @@ iBool isPinned_DocumentWidget_(const iDocumentWidget *d) {
975 975
976static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) { 976static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
977 iWidget *w = as_Widget(d); 977 iWidget *w = as_Widget(d);
978 showCollapsed_Widget(findChild_Widget(root_Widget(as_Widget(d)), "document.pinned"), 978 showCollapsed_Widget(findChild_Widget(root_Widget(w), "document.pinned"),
979 isPinned_DocumentWidget_(d)); 979 isPinned_DocumentWidget_(d));
980} 980}
981 981
982void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 982static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
983 setUrl_GmDocument(d->doc, d->mod.url);
984 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
985 documentRunsInvalidated_DocumentWidget_(d); 983 documentRunsInvalidated_DocumentWidget_(d);
986 updateWindowTitle_DocumentWidget_(d); 984 updateWindowTitle_DocumentWidget_(d);
987 updateVisible_DocumentWidget_(d); 985 updateVisible_DocumentWidget_(d);
@@ -997,7 +995,23 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
997 d->flags |= otherRootByDefault_DocumentWidgetFlag; 995 d->flags |= otherRootByDefault_DocumentWidgetFlag;
998 } 996 }
999 } 997 }
1000 showOrHidePinningIndicator_DocumentWidget_(d); 998 showOrHidePinningIndicator_DocumentWidget_(d);
999}
1000
1001void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1002 setUrl_GmDocument(d->doc, d->mod.url);
1003 setSource_GmDocument(d->doc,
1004 source,
1005 documentWidth_DocumentWidget_(d),
1006 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1007 : partial_GmDocumentUpdate);
1008 documentWasChanged_DocumentWidget_(d);
1009}
1010
1011static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1012 iRelease(d->doc);
1013 d->doc = ref_Object(newDoc);
1014 documentWasChanged_DocumentWidget_(d);
1001} 1015}
1002 1016
1003static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1017static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
@@ -1063,13 +1077,13 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1063 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1077 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1064 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1078 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat);
1065 translate_Lang(src); 1079 translate_Lang(src);
1080 d->state = ready_RequestState;
1066 setSource_DocumentWidget(d, src); 1081 setSource_DocumentWidget(d, src);
1067 updateTheme_DocumentWidget_(d); 1082 updateTheme_DocumentWidget_(d);
1068 reset_SmoothScroll(&d->scrollY); 1083 reset_SmoothScroll(&d->scrollY);
1069 init_Anim(&d->sideOpacity, 0); 1084 init_Anim(&d->sideOpacity, 0);
1070 init_Anim(&d->altTextOpacity, 0); 1085 init_Anim(&d->altTextOpacity, 0);
1071 resetWideRuns_DocumentWidget_(d); 1086 resetWideRuns_DocumentWidget_(d);
1072 d->state = ready_RequestState;
1073} 1087}
1074 1088
1075static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1089static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1174,7 +1188,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1174 } 1188 }
1175} 1189}
1176 1190
1177static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1191static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1192 const iGmResponse *response,
1193 iGmDocument *cachedDoc,
1178 const iBool isInitialUpdate) { 1194 const iBool isInitialUpdate) {
1179 if (d->state == ready_RequestState) { 1195 if (d->state == ready_RequestState) {
1180 return; 1196 return;
@@ -1247,6 +1263,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1247 docFormat = gemini_GmDocumentFormat; 1263 docFormat = gemini_GmDocumentFormat;
1248 setRange_String(&d->sourceMime, param); 1264 setRange_String(&d->sourceMime, param);
1249 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1265 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1266 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
1250 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { 1267 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) {
1251 const char *linkTitle = 1268 const char *linkTitle =
1252 startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; 1269 startsWith_String(mimeStr, "image/") ? "Image" : "Audio";
@@ -1300,7 +1317,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1300 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); 1317 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset))));
1301 } 1318 }
1302 } 1319 }
1303 if (setSource) { 1320 if (cachedDoc) {
1321 replaceDocument_DocumentWidget_(d, cachedDoc);
1322 }
1323 else if (setSource) {
1304 setSource_DocumentWidget(d, &str); 1324 setSource_DocumentWidget(d, &str);
1305 } 1325 }
1306 deinit_String(&str); 1326 deinit_String(&str);
@@ -1386,7 +1406,8 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1386 clear_ObjectList(d->media); 1406 clear_ObjectList(d->media);
1387 delete_Gempub(d->sourceGempub); 1407 delete_Gempub(d->sourceGempub);
1388 d->sourceGempub = NULL; 1408 d->sourceGempub = NULL;
1389 reset_GmDocument(d->doc); 1409 iRelease(d->doc);
1410 d->doc = new_GmDocument();
1390 resetWideRuns_DocumentWidget_(d); 1411 resetWideRuns_DocumentWidget_(d);
1391 d->state = fetching_RequestState; 1412 d->state = fetching_RequestState;
1392 /* Do the fetch. */ { 1413 /* Do the fetch. */ {
@@ -1397,10 +1418,11 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1397 d->sourceStatus = success_GmStatusCode; 1418 d->sourceStatus = success_GmStatusCode;
1398 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1419 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1399 set_Block(&d->sourceContent, &resp->body); 1420 set_Block(&d->sourceContent, &resp->body);
1400 updateDocument_DocumentWidget_(d, resp, iTrue); 1421 updateDocument_DocumentWidget_(d, resp, recent->cachedDoc, iTrue);
1401 postProcessRequestContent_DocumentWidget_(d, iTrue); 1422 setCachedDocument_History(d->mod.history, d->doc);
1402 } 1423 }
1403 d->state = ready_RequestState; 1424 d->state = ready_RequestState;
1425 postProcessRequestContent_DocumentWidget_(d, iTrue);
1404 init_Anim(&d->altTextOpacity, 0); 1426 init_Anim(&d->altTextOpacity, 0);
1405 reset_SmoothScroll(&d->scrollY); 1427 reset_SmoothScroll(&d->scrollY);
1406 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1428 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
@@ -1652,11 +1674,12 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1652 } 1674 }
1653 case categorySuccess_GmStatusCode: 1675 case categorySuccess_GmStatusCode:
1654 //reset_SmoothScroll(&d->scrollY); 1676 //reset_SmoothScroll(&d->scrollY);
1655 reset_GmDocument(d->doc); /* new content incoming */ 1677 iRelease(d->doc); /* new content incoming */
1678 d->doc = new_GmDocument();
1656 delete_Gempub(d->sourceGempub); 1679 delete_Gempub(d->sourceGempub);
1657 d->sourceGempub = NULL; 1680 d->sourceGempub = NULL;
1658 resetWideRuns_DocumentWidget_(d); 1681 resetWideRuns_DocumentWidget_(d);
1659 updateDocument_DocumentWidget_(d, resp, iTrue); 1682 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
1660 break; 1683 break;
1661 case categoryRedirect_GmStatusCode: 1684 case categoryRedirect_GmStatusCode:
1662 if (isEmpty_String(&resp->meta)) { 1685 if (isEmpty_String(&resp->meta)) {
@@ -1707,7 +1730,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1707 switch (category_GmStatusCode(statusCode)) { 1730 switch (category_GmStatusCode(statusCode)) {
1708 case categorySuccess_GmStatusCode: 1731 case categorySuccess_GmStatusCode:
1709 /* More content available. */ 1732 /* More content available. */
1710 updateDocument_DocumentWidget_(d, resp, iFalse); 1733 updateDocument_DocumentWidget_(d, resp, NULL, iFalse);
1711 break; 1734 break;
1712 default: 1735 default:
1713 break; 1736 break;
@@ -2318,6 +2341,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2318 (startsWithCase_String(meta_GmRequest(d->request), "text/") || 2341 (startsWithCase_String(meta_GmRequest(d->request), "text/") ||
2319 !cmp_String(&d->sourceMime, mimeType_Gempub))) { 2342 !cmp_String(&d->sourceMime, mimeType_Gempub))) {
2320 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request)); 2343 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request));
2344 setCachedDocument_History(d->mod.history, d->doc); /* keeps a ref */
2321 unlockResponse_GmRequest(d->request); 2345 unlockResponse_GmRequest(d->request);
2322 } 2346 }
2323 } 2347 }
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 3b72cc60..784fabdd 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -710,12 +710,12 @@ iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine
710} 710}
711 711
712static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 712static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
713 size_t index = line->offset;
713 if (x <= 0) { 714 if (x <= 0) {
714 return line->offset; 715 return index;
715 } 716 }
716 const char *endPos; 717 const char *endPos;
717 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 718 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
718 size_t index = line->offset;
719 if (endPos == constEnd_String(&line->text)) { 719 if (endPos == constEnd_String(&line->text)) {
720 index += line->len; 720 index += line->len;
721 } 721 }
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index cb32a29c..70553488 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -41,20 +41,20 @@ struct Impl_InputWidgetContentPadding {
41 41
42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); 42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context);
43 43
44void setHint_InputWidget (iInputWidget *, const char *hintText); 44void setHint_InputWidget (iInputWidget *, const char *hintText);
45void setMode_InputWidget (iInputWidget *, enum iInputMode mode); 45void setMode_InputWidget (iInputWidget *, enum iInputMode mode);
46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); 46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
47void setText_InputWidget (iInputWidget *, const iString *text); 47void setText_InputWidget (iInputWidget *, const iString *text);
48void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 48void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50void setCursor_InputWidget (iInputWidget *, size_t pos); 50void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); 52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
55void begin_InputWidget (iInputWidget *); 55void begin_InputWidget (iInputWidget *);
56void end_InputWidget (iInputWidget *, iBool accept); 56void end_InputWidget (iInputWidget *, iBool accept);
57void selectAll_InputWidget (iInputWidget *); 57void selectAll_InputWidget (iInputWidget *);
58 58
59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); 59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); 60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive);
@@ -62,9 +62,8 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl);
62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); 62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); 63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape);
64 64
65const iString * text_InputWidget (const iInputWidget *); 65iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *);
66iInputWidgetContentPadding 66const iString * text_InputWidget (const iInputWidget *);
67 contentPadding_InputWidget (const iInputWidget *);
68 67
69iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { 68iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) {
70 iInputWidget *d = new_InputWidget(maxLen); 69 iInputWidget *d = new_InputWidget(maxLen);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 44ed795b..a940b0cb 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -206,7 +206,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
206 } 206 }
207 } 207 }
208 int colorEscape = none_ColorId; 208 int colorEscape = none_ColorId;
209 if (startsWith_String(&d->label, "\r")) { 209 if (startsWith_String(&d->label, "\v")) {
210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ 210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */
211 } 211 }
212 if (isHover_LabelWidget_(d)) { 212 if (isHover_LabelWidget_(d)) {
diff --git a/src/ui/text.c b/src/ui/text.c
index b0c4f557..889aa2e4 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -954,10 +954,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
954 prevCh = 0; 954 prevCh = 0;
955 continue; 955 continue;
956 } 956 }
957 if (ch == '\r') { /* color change */ 957 if (ch == '\v') { /* color change */
958 iChar esc = nextChar_(&chPos, args->text.end); 958 iChar esc = nextChar_(&chPos, args->text.end);
959 int colorNum = args->color; 959 int colorNum = args->color;
960 if (esc == '\r') { /* Extended range. */ 960 if (esc == '\v') { /* Extended range. */
961 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; 961 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
962 colorNum = esc - asciiBase_ColorEscape; 962 colorNum = esc - asciiBase_ColorEscape;
963 } 963 }