}
}
@@ -983,10 +1062,14 @@
/*
** This is a helper function for search_stext(). Writing into pOut
** the search text obtained from pIn according to zMimetype.
+**
+** The title of the document is the first line of text. All subsequent
+** lines are the body. If the document has no title, the first line
+** is blank.
*/
static void get_stext_by_mimetype(
Blob *pIn,
const char *zMimetype,
Blob *pOut
@@ -994,41 +1077,74 @@
Blob html, title;
blob_init(&html, 0, 0);
blob_init(&title, 0, 0);
if( zMimetype==0 ) zMimetype = "text/plain";
if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
- wiki_convert(pIn, &html, 0);
+ Blob tail;
+ blob_init(&tail, 0, 0);
+ if( wiki_find_title(pIn, &title, &tail) ){
+ blob_appendf(pOut, "%s\n", blob_str(&title));
+ wiki_convert(&tail, &html, 0);
+ blob_reset(&tail);
+ }else{
+ blob_append(pOut, "\n", 1);
+ wiki_convert(pIn, &html, 0);
+ }
html_to_plaintext(blob_str(&html), pOut);
}else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
markdown_to_html(pIn, &title, &html);
+ if( blob_size(&title) ){
+ blob_appendf(pOut, "%s\n", blob_str(&title));
+ }else{
+ blob_append(pOut, "\n", 1);
+ }
html_to_plaintext(blob_str(&html), pOut);
}else if( fossil_strcmp(zMimetype,"text/html")==0 ){
+ if( doc_is_embedded_html(pIn, &title) ){
+ blob_appendf(pOut, "%s\n", blob_str(&title));
+ }
html_to_plaintext(blob_str(pIn), pOut);
}else{
- *pOut = *pIn;
- blob_init(pIn, 0, 0);
+ blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
}
blob_reset(&html);
blob_reset(&title);
}
/*
** Query pQuery is pointing at a single row of output. Append a text
** representation of every text-compatible column to pAccum.
*/
-static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){
+static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){
int n = db_column_count(pQuery);
int i;
+ const char *zMime = 0;
+ if( iTitle>=0 && iTitle0)"
" ||')'"
" FROM event WHERE objid=:x AND type='ci'");
+ if( isPlainText<0 ){
+ isPlainText = db_get_boolean("timeline-plaintext",0);
+ }
db_bind_int(&q, ":x", rid);
if( db_step(&q)==SQLITE_ROW ){
- db_column_blob(&q, 0, pOut);
blob_append(pOut, "\n", 1);
+ if( isPlainText ){
+ db_column_blob(&q, 0, pOut);
+ }else{
+ Blob x;
+ blob_init(&x,0,0);
+ db_column_blob(&q, 0, &x);
+ get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut);
+ blob_reset(&x);
+ }
}
db_reset(&q);
break;
}
case 't': { /* Tickets */
static Stmt q1;
- Blob raw;
+ static int iTitle = -1;
db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
- blob_init(&raw,0,0);
db_bind_int(&q1, ":rid", rid);
if( db_step(&q1)==SQLITE_ROW ){
- append_all_ticket_fields(&raw, &q1);
+ if( iTitle<0 ){
+ int n = db_column_count(&q1);
+ for(iTitle=0; iTitle0 ){
+ blob_reset(&cache.stext);
+ }else{
+ blob_init(&cache.stext,0,0);
+ }
+ cache.cType = cType;
+ cache.rid = rid;
+ if( cType==0 ) return 0;
+ search_stext(cType, rid, zName, &cache.stext);
+ z = blob_str(&cache.stext);
+ for(i=0; z[i] && z[i]!='\n'; i++){}
+ cache.nTitle = i;
+ }
+ if( pnTitle ) *pnTitle = cache.nTitle;
+ return blob_str(&cache.stext);
+}
/*
** COMMAND: test-search-stext
**
** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1131,10 +1303,30 @@
if( g.argc!=5 ) usage("TYPE RID NAME");
search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
fossil_print("%s\n",blob_str(&out));
blob_reset(&out);
}
+
+/*
+** COMMAND: test-convert-stext
+**
+** Usage: fossil test-convert-stext FILE MIMETYPE
+**
+** Read the content of FILE and convert it to stext according to MIMETYPE.
+** Send the result to standard output.
+*/
+void test_convert_stext(void){
+ Blob in, out;
+ db_find_and_open_repository(0,0);
+ if( g.argc!=4 ) usage("FILENAME MIMETYPE");
+ blob_read_from_file(&in, g.argv[2]);
+ blob_init(&out, 0, 0);
+ get_stext_by_mimetype(&in, g.argv[3], &out);
+ fossil_print("%s\n",blob_str(&out));
+ blob_reset(&in);
+ blob_reset(&out);
+}
/* The schema for the full-text index
*/
static const char zFtsSchema[] =
@ -- One entry for each possible search result
@@ -1145,20 +1337,21 @@
@ name TEXT, -- Additional document description
@ idxed BOOLEAN, -- True if currently in the index
@ label TEXT, -- Label to print on search results
@ url TEXT, -- URL to access this document
@ mtime DATE, -- Date when document created
+@ bx TEXT, -- Temporary "body" content cache
@ UNIQUE(type,rid)
@ );
@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
@ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
@ SELECT rowid, type, rid, name, idxed, label, url, mtime,
-@ stext(type,rid,name) AS 'stext'
+@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body'
@ FROM ftsdocs;
@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
-@ USING fts4(content="ftscontent", stext);
+@ USING fts4(content="ftscontent", title, body%s);
;
static const char zFtsDrop[] =
@ DROP TABLE IF EXISTS "%w".ftsidx;
@ DROP VIEW IF EXISTS "%w".ftscontent;
@ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1168,13 +1361,15 @@
** Create or drop the tables associated with a full-text index.
*/
static int searchIdxExists = -1;
void search_create_index(void){
const char *zDb = db_name("repository");
+ int useStemmer = db_get_boolean("search-stemmer",0);
+ const char *zExtra = useStemmer ? ",tokenize=porter" : "";
search_sql_setup(g.db);
- db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/,
- zDb, zDb, zDb, zDb, zDb);
+ db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/,
+ zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/);
searchIdxExists = 1;
}
void search_drop_index(void){
const char *zDb = db_name("repository");
db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1292,34 +1487,39 @@
db_multi_exec(
"DELETE FROM ftsdocs WHERE type='d'"
" AND rid NOT IN (SELECT rid FROM current_docs)"
);
db_multi_exec(
- "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)"
+ "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)"
" SELECT 'd', rid, name, 0,"
- " printf('Document: %%s',name),"
+ " title('d',rid,name),"
+ " body('d',rid,name),"
" printf('/doc/%q/%%s',urlencode(name)),"
" %.17g"
" FROM current_docs",
zBrUuid, rTime
);
db_multi_exec(
- "INSERT INTO ftsidx(docid,stext)"
- " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
+ "INSERT INTO ftsidx(docid,title,body)"
+ " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed"
);
db_multi_exec(
- "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
+ "UPDATE ftsdocs SET"
+ " idxed=1,"
+ " bx=NULL,"
+ " label='Document: '||label"
+ " WHERE type='d' AND NOT idxed"
);
}
/*
** Deal with all of the unindexed 'c' terms in FTSDOCS
*/
static void search_update_checkin_index(void){
db_multi_exec(
- "INSERT INTO ftsidx(docid,stext)"
- " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs"
+ "INSERT INTO ftsidx(docid,title,body)"
+ " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs"
" WHERE type='c' AND NOT idxed;"
);
db_multi_exec(
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
" SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
@@ -1336,19 +1536,20 @@
/*
** Deal with all of the unindexed 't' terms in FTSDOCS
*/
static void search_update_ticket_index(void){
db_multi_exec(
- "INSERT INTO ftsidx(docid,stext)"
- " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs"
+ "INSERT INTO ftsidx(docid,title,body)"
+ " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs"
" WHERE type='t' AND NOT idxed;"
);
if( db_changes()==0 ) return;
db_multi_exec(
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
" SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
- " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime)),"
+ " printf('Ticket: %%s (%%s)',title('t',tkt_id,null),"
+ " datetime(tkt_mtime)),"
" printf('/tktview/%%.20s',tkt_uuid),"
" tkt_mtime"
" FROM ftsdocs, ticket"
" WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
" AND ticket.tkt_id=ftsdocs.rid"
@@ -1358,12 +1559,12 @@
/*
** Deal with all of the unindexed 'w' terms in FTSDOCS
*/
static void search_update_wiki_index(void){
db_multi_exec(
- "INSERT INTO ftsidx(docid,stext)"
- " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs"
+ "INSERT INTO ftsidx(docid,title,body)"
+ " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs"
" WHERE type='w' AND NOT idxed;"
);
if( db_changes()==0 ) return;
db_multi_exec(
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1416,19 +1617,22 @@
** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
**
** The "fossil fts-config" command configures the full-text search capabilities
** of the repository. Subcommands:
**
-** reindex Rebuild the search index. Create it if it does
-** not already exist
+** reindex Rebuild the search index. This is a no-op if
+** index search is disabled
**
** index (on|off) Turn the search index on or off
**
** enable cdtw Enable various kinds of search. c=Check-ins,
** d=Documents, t=Tickets, w=Wiki.
**
** disable cdtw Disable versious kinds of search
+**
+** stemmer (on|off) Turn the Porter stemmer on or off for indexed
+** search. (Unindexed search is never stemmed.)
**
** The current search settings are displayed after any changes are applied.
** Run this command with no arguments to simply see the settings.
*/
void test_fts_cmd(void){
@@ -1435,16 +1639,17 @@
static const struct { int iCmd; const char *z; } aCmd[] = {
{ 1, "reindex" },
{ 2, "index" },
{ 3, "disable" },
{ 4, "enable" },
+ { 5, "stemmer" },
};
static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
- { "search-ckin", "check-in search:", "c" },
- { "search-doc", "document search:", "d" },
- { "search-tkt", "ticket search:", "t" },
- { "search-wiki", "wiki search:", "w" },
+ { "search-ckin", "check-in search:", "c" },
+ { "search-doc", "document search:", "d" },
+ { "search-tkt", "ticket search:", "t" },
+ { "search-wiki", "wiki search:", "w" },
};
char *zSubCmd;
int i, j, n;
int iCmd = 0;
int iAction = 0;
@@ -1464,11 +1669,11 @@
return;
}
iCmd = aCmd[i].iCmd;
}
if( iCmd==1 ){
- iAction = 2;
+ if( search_index_exists() ) iAction = 2;
}
if( iCmd==2 ){
if( g.argc<3 ) usage("index (on|off)");
iAction = 1 + is_truth(g.argv[3]);
}
@@ -1475,18 +1680,23 @@
db_begin_transaction();
/* Adjust search settings */
if( iCmd==3 || iCmd==4 ){
const char *zCtrl;
- if( g.argc<4 ) usage("enable STRING");
+ if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd));
zCtrl = g.argv[3];
for(j=0; j=1 ){
search_drop_index();
}
@@ -1497,14 +1707,16 @@
/* Always show the status before ending */
for(i=0; iCurrently using an SQLite FTS4 search index. This makes search
@ run faster, especially on large repositories, but takes up space.
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
@
@
}else{
@
The SQLite FTS4 search index is disabled. All searching will be
@ a full-text scan. This usually works fine, but can be slow for
@ larger repositories.