James Moger
2012-03-14 6722961db100d58f8290d51768943fc6b416f7a9
A bunch more Lucene work

* Only list user-accessible repositories
* Syntax-highlight matched blob fragments
* Improve look and layout of search results
5 files modified
127 ■■■■ changed files
resources/gitblit.css 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/LuceneUtils.java 61 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/StringUtils.java 7 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/LucenePage.html 20 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/LucenePage.java 15 ●●●● patch | view | raw | blame | history
resources/gitblit.css
@@ -267,7 +267,7 @@
}
div.searchResult {
    padding:5px;
    padding: 10px 5px 10px 5px;
}
div.searchResult .summary {
@@ -300,18 +300,24 @@
    padding: 0 2px;
}
div.searchResult .ellipses {
    font-family: sans-serif;
    font-size: 9px;
    font-weight: normal;
    background-color: #eee;
    border: 1px solid #ccc;
    padding: 0 3px;
    margin: 0px;
div.searchResult .ellipses {
    padding-left:25px;
    color: #aaa;
}
div.searchResult pre {
    margin: 1px 0px;
    border: 0px;
}
div.searchResult .text {
    border-left: 5px solid #EEEEEE;
    padding: 0 0 0 15px;
}
div.searchResult ol {
    margin-bottom: 0px !important;
}
div.header, div.commitHeader, table.repositories th {
src/com/gitblit/utils/LuceneUtils.java
@@ -21,6 +21,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -903,7 +904,8 @@
                Document doc = searcher.doc(docId);
                SearchResult result = createSearchResult(doc, hits[i].score);
                String content = doc.get(FIELD_CONTENT);
                result.fragment = getHighlightedFragment(analyzer, query, content);
                result.fragment = getHighlightedFragment(analyzer, query, content, result);
                results.add(result);
            }
        } catch (Exception e) {
@@ -913,28 +915,44 @@
    }
    
    private static String getHighlightedFragment(Analyzer analyzer, Query query,
            String content) throws IOException, InvalidTokenOffsetsException {
        content = content == null ? "":StringUtils.escapeForHtml(content, false);
            String content, SearchResult result) throws IOException, InvalidTokenOffsetsException {
        content = content == null ? "":StringUtils.escapeForHtml(content, false);
        TokenStream stream = TokenSources.getTokenStream("content", content, analyzer);
        QueryScorer scorer = new QueryScorer(query, "content");
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, 150);
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"highlight\">", "</span>");
        Highlighter highlighter = new Highlighter(formatter, scorer);
        Fragmenter fragmenter;
        
        if (ObjectType.commit == result.type) {
            fragmenter = new SimpleSpanFragmenter(scorer, 1024);
        } else {
            fragmenter = new SimpleSpanFragmenter(scorer, 150);
        }
        // use an artificial delimiter for the token
        String termTag = "<!--[";
        String termTagEnd = "]-->";
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(termTag, termTagEnd);
        Highlighter highlighter = new Highlighter(formatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        String [] fragments = highlighter.getBestFragments(stream, content, 5);
        if (ArrayUtils.isEmpty(fragments)) {
            return content;
        }
        if (fragments.length == 1) {
            return "<pre>" + fragments[0] + "</pre>";
            if (ObjectType.blob  == result.type) {
                return "";
            }
            return "<pre class=\"text\">" + content + "</pre>";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0, len = fragments.length; i < len; i++) {
            String fragment = fragments[i].trim();
            sb.append("<pre>");
            sb.append(fragment);
            String fragment = fragments[i];
            // resurrect the raw fragment from removing the artificial delimiters
            String raw = fragment.replace(termTag, "").replace(termTagEnd, "");
            sb.append(getPreTag(result, raw, content));
            // replace the artificial delimiter with html tags
            String html = fragment.replace(termTag, "<span class=\"highlight\">").replace(termTagEnd, "</span>");
            sb.append(html);
            sb.append("</pre>");
            if (i < len - 1) {
                sb.append("<span class=\"ellipses\">...</span><br/>");
@@ -942,6 +960,21 @@
        }
        return sb.toString();
    }
    private static String getPreTag(SearchResult result, String fragment, String content) {
        String pre = "<pre class=\"text\">";
        if (ObjectType.blob  == result.type) {
            int line = StringUtils.countLines(content.substring(0, content.indexOf(fragment)));
            int lastDot = result.path.lastIndexOf('.');
            if (lastDot > -1) {
                String ext = result.path.substring(lastDot + 1).toLowerCase();
                pre = MessageFormat.format("<pre class=\"prettyprint linenums:{0,number,0} lang-{1}\">", line, ext);
            } else {
                pre = MessageFormat.format("<pre class=\"prettyprint linenums:{0,number,0}\">", line);
            }
        }
        return pre;
    }
    /**
     * Close all the index writers and searchers
src/com/gitblit/utils/StringUtils.java
@@ -485,4 +485,11 @@
        }
        return value;
    }
    public static int countLines(String value) {
        if (isEmpty(value)) {
            return 0;
        }
        return value.split("\n").length;
    }
}
src/com/gitblit/wicket/pages/LucenePage.html
@@ -3,8 +3,17 @@
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
      xml:lang="en"  
      lang="en"> 
<body onload="document.getElementById('fragment').focus();">
<!-- contribute google-code-prettify resources to the page header -->
<wicket:head>
  <wicket:link>
       <link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
    <script type="text/javascript" src="prettify/prettify.js"></script>
  </wicket:link>
</wicket:head>
<wicket:extend>
<body onload="document.getElementById('query').focus(); prettyPrint();">
    <div class="pageTitle">
        <h2><wicket:message key="gb.search"></wicket:message></h2>
    </div>
@@ -17,7 +26,7 @@
            <div class="span9">
                <div>
                    <h3><wicket:message key="gb.query"></wicket:message></h3>
                    <input class="span8" wicket:id="query" placeholder="enter search text"></input>
                    <input class="span8" id="query" wicket:id="query" placeholder="enter search text"></input>
                    <button class="btn btn-primary" type="submit" value="Search"><wicket:message key="gb.search"></wicket:message></button>
                </div>
                <div style="margin-top:10px;">
@@ -45,12 +54,11 @@
        <div><i wicket:id="type"></i><span class="summary" wicket:id="summary"></span></div>
        <div class="body">
            <div class="fragment" wicket:id="fragment"></div>
            <span class="author" wicket:id="author"></span> committed to <span class="repository" wicket:id="repository"></span>:<span class="branch" wicket:id="branch"></span><br/>
            <span class="date" wicket:id="date"></span>
            <hr/>
            <div><span class="author" wicket:id="author"></span> <span class="date" ><wicket:message key="gb.authored"></wicket:message> <span class="date" wicket:id="date"></span></span></div>
            <span class="repository" wicket:id="repository"></span>:<span class="branch" wicket:id="branch"></span>
        </div>
    </div>
    </div>
</wicket:extend>
</body>
</wicket:extend>
</html>
src/com/gitblit/wicket/pages/LucenePage.java
@@ -32,10 +32,14 @@
import com.gitblit.Constants.SearchType;
import com.gitblit.GitBlit;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SearchResult;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.LuceneUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
@@ -110,7 +114,14 @@
                setResponsePage(LucenePage.class, params);
            }
        };
        ListMultipleChoice<String> selections = new ListMultipleChoice<String>("repositories", repositoriesModel, GitBlit.self().getRepositoryList());
        UserModel user = GitBlitWebSession.get().getUser();
        List<String> availableRepositories = new ArrayList<String>();
        for (RepositoryModel model : GitBlit.self().getRepositoryModels(user)) {
            availableRepositories.add(model.name);
        }
        ListMultipleChoice<String> selections = new ListMultipleChoice<String>("repositories",
                repositoriesModel, availableRepositories, new StringChoiceRenderer());
        selections.setMaxRows(10);
        form.add(selections);
        form.add(new TextField<String>("query", queryModel));
@@ -153,7 +164,7 @@
                item.add(new LinkPanel("repository", null, sr.repository, SummaryPage.class, WicketUtils.newRepositoryParameter(sr.repository)));
                item.add(new LinkPanel("branch", "branch", StringUtils.getRelativePath(Constants.R_HEADS, sr.branch), LogPage.class, WicketUtils.newObjectParameter(sr.repository, sr.branch)));
                item.add(new Label("author", sr.author));
                item.add(WicketUtils.createTimestampLabel("date", sr.date, getTimeZone()));
                item.add(WicketUtils.createDatestampLabel("date", sr.date, getTimeZone()));
            }
        };
        add(resultsView.setVisible(results.size() > 0));