Skip to content

Commit

Permalink
Implement match highlighting
Browse files Browse the repository at this point in the history
Configurable using g:cpsm_highlight_mode.

Fixes #2.
  • Loading branch information
nixprime committed Jun 6, 2015
1 parent 8527824 commit ecec82f
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 92 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ Installation
Options
-------

- To control how matched characters are highlighted, set

let g:cpsm_highlight_mode = (highlight mode)

Valid highlight modes are:

- "none": Do not highlight any match characters.

- "basic": Highlight the entire region between the leftmost and rightmost
matched characters.

- "detailed": Highlight each matched character.

The default is "detailed". The highlight group used to highlight matched
characters is "CtrlPMatch" (the same as for CtrlP's default matcher).

- By default, cpsm will automatically detect the number of matcher threads
based on the available hardware concurrency. To limit the number of threads
that cpsm can use, add
Expand Down
34 changes: 24 additions & 10 deletions autoload/cpsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,36 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function

import sys
import vim

script_dir = vim.eval("s:script_dir")
sys.path.append(script_dir)
import cpsm_py

def _escape_and_quote(s):
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'

def ctrlp_match():
# TODO: a:regex is unimplemented.
results = cpsm_py.ctrlp_match(
vim.eval("a:items"), vim.eval("a:str"),
limit=int(vim.eval("a:limit")), mmode=vim.eval("a:mmode"),
ispath=int(vim.eval("a:ispath")), crfile=vim.eval("a:crfile"),
max_threads=int(vim.eval("g:cpsm_max_threads")),
unicode=int(vim.eval("g:cpsm_unicode")))
# Escape backslashes and ".
vim.command("let s:results = [%s]" % ",".join(
'"%s"' % r.replace("\\", "\\\\").replace('"', '\\"')
for r in results))
try:
results, regexes = cpsm_py.ctrlp_match(
vim.eval("a:items"), vim.eval("a:str"),
limit=int(vim.eval("a:limit")), mmode=vim.eval("a:mmode"),
ispath=int(vim.eval("a:ispath")), crfile=vim.eval("a:crfile"),
max_threads=int(vim.eval("g:cpsm_max_threads")),
unicode=int(vim.eval("g:cpsm_unicode")),
highlight_mode=vim.eval("g:cpsm_highlight_mode"))
# Escape backslashes and ".
vim.command("let s:results = [%s]" % ",".join(
map(_escape_and_quote, results)))
vim.command("let s:regexes = [%s]" % ",".join(
map(_escape_and_quote, regexes)))
for r in regexes:
print(r)
except Exception as ex:
vim.command("let s:results = [%s]" % _escape_and_quote(
"ERROR: " + str(ex)))
vim.command("let s:regexes = []")
7 changes: 7 additions & 0 deletions autoload/cpsm.vim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
" limitations under the License.

" Global variables and defaults
if !exists('g:cpsm_highlight_mode')
let g:cpsm_highlight_mode = 'detailed'
endif
if !exists('g:cpsm_max_threads')
let g:cpsm_max_threads = 0
endif
Expand All @@ -28,6 +31,10 @@ execute 'pyfile ' . s:script_dir . '/cpsm.py'
function cpsm#CtrlPMatch(items, str, limit, mmode, ispath, crfile, regex)
py ctrlp_match()
call clearmatches()
" Apply highlight regexes.
for r in s:regexes
call matchadd('CtrlPMatch', r)
endfor
" CtrlP does this match to hide the leading > in results.
call matchadd('CtrlPLinePre', '^>')
return s:results
Expand Down
2 changes: 1 addition & 1 deletion src/cpsm_cli_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ int main(int argc, char** argv) {
matches.clear();
for (auto const& line : lines) {
Match<boost::string_ref> m(line);
if (matcher.match(m.item, m, &buf, &buf2)) {
if (matcher.match(m.item, m, nullptr, &buf, &buf2)) {
matches.emplace_back(std::move(m));
if (limit) {
std::push_heap(matches.begin(), matches.end());
Expand Down
91 changes: 90 additions & 1 deletion src/ctrlp_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,61 @@

#include "ctrlp_util.h"

#include <sstream>
#include <stdexcept>
#include <utility>

#include "path_util.h"
#include "str_util.h"

namespace cpsm {

namespace {

// Groups match positions into matched intervals.
std::set<std::pair<std::size_t, std::size_t>> group_positions_detailed(
std::set<CharCount> const& positions) {
std::set<std::pair<std::size_t, std::size_t>> group;
std::size_t begin = 0;
std::size_t end = 0;
for (CharCount const pos : positions) {
if (pos != end) {
// End of previous group, start of new group.
if (begin != end) {
group.emplace(begin, end);
}
begin = end = pos;
}
end++;
}
if (begin != end) {
group.emplace(begin, end);
}
return group;
}

// Returns a single match group spanning from the first to last match.
std::set<std::pair<std::size_t, std::size_t>> group_positions_basic(
std::set<CharCount> const& positions) {
std::set<std::pair<std::size_t, std::size_t>> group;
if (!positions.empty()) {
group.emplace(*positions.cbegin(), (*positions.crbegin()) + 1);
}
return group;
}

std::set<std::pair<std::size_t, std::size_t>> group_positions(
boost::string_ref const mode, std::set<CharCount> const& positions) {
if (mode == "basic") {
return group_positions_basic(positions);
} else if (mode == "detailed") {
return group_positions_detailed(positions);
} else {
throw Error("unknown highlight mode '", mode, "'");
}
}

} // anonymous namespace

std::function<boost::string_ref(boost::string_ref)> match_mode_item_substr_fn(
boost::string_ref mmode) {
if (mmode.empty() || mmode == "full-line") {
Expand All @@ -43,4 +91,45 @@ std::function<boost::string_ref(boost::string_ref)> match_mode_item_substr_fn(
throw Error("unknown match mode ", mmode);
}

void get_highlight_regexes(boost::string_ref const mode,
boost::string_ref const item,
std::set<CharCount> const& positions,
std::vector<std::string>& regexes) {
for (auto const group : group_positions(mode, positions)) {
// Each match group's regex has the same structure:
// - "\V": very nomagic (only "\" needs to be escaped)
// - "\C": forces case sensitivity
// - "\^": beginning of string
// - "> ": appears at the start of each line
// - characters in the item before the match
// - "\zs": starts the match
// - characters in the match group
// - "\ze": ends the match
// - characters in the item after the match
// - "\$": end of string
std::size_t i = 0;
std::string regex = R"(\V\C\^> )";
auto const write_char = [&](std::size_t i) {
if (item[i] == '\\') {
regex += R"(\\)";
} else {
regex += item[i];
}
};
for (; i < group.first; i++) {
write_char(i);
}
regex += R"(\zs)";
for (; i < group.second; i++) {
write_char(i);
}
regex += R"(\ze)";
for (; i < item.size(); i++) {
write_char(i);
}
regex += R"(\$)";
regexes.emplace_back(std::move(regex));
}
}

} // namespace cpsm
10 changes: 10 additions & 0 deletions src/ctrlp_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,26 @@
#define CPSM_CTRLP_UTIL_H_

#include <functional>
#include <set>
#include <string>

#include <boost/utility/string_ref.hpp>

#include "str_util.h"

namespace cpsm {

// Returns an item substring function (see MatcherOpts::item_substr_fn) for
// the given CtrlP match mode.
std::function<boost::string_ref(boost::string_ref)> match_mode_item_substr_fn(
boost::string_ref mmode);

// Appends a set of Vim regexes to highlight the bytes at positions in item for
// the given highlight mode.
void get_highlight_regexes(boost::string_ref mode, boost::string_ref item,
std::set<CharCount> const& positions,
std::vector<std::string>& regexes);

} // namespace cpsm

#endif /* CPSM_CTRLP_UTIL_H_ */
Loading

0 comments on commit ecec82f

Please sign in to comment.