* Only list user-accessible repositories * Syntax-highlight matched blob fragments * Improve look and layout of search resultstags/v0.9.0
@@ -267,7 +267,7 @@ img.gravatar { | |||
} | |||
div.searchResult { | |||
padding:5px; | |||
padding: 10px 5px 10px 5px; | |||
} | |||
div.searchResult .summary { | |||
@@ -300,18 +300,24 @@ div.searchResult .highlight { | |||
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 { |
@@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; | |||
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 @@ public class LuceneUtils { | |||
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 @@ public class LuceneUtils { | |||
} | |||
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 @@ public class LuceneUtils { | |||
} | |||
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 |
@@ -485,4 +485,11 @@ public class StringUtils { | |||
} | |||
return value; | |||
} | |||
public static int countLines(String value) { | |||
if (isEmpty(value)) { | |||
return 0; | |||
} | |||
return value.split("\n").length; | |||
} | |||
} |
@@ -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> |
@@ -32,10 +32,14 @@ import org.eclipse.jgit.lib.Repository; | |||
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 @@ public class LucenePage extends RootPage { | |||
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 @@ public class LucenePage extends RootPage { | |||
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)); |