2018-08-21 05:27:52 +00:00
|
|
|
// Copyright 2017-2018 ccls Authors
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2017-03-26 21:40:34 +00:00
|
|
|
#include "working_files.h"
|
|
|
|
|
2018-05-27 19:24:56 +00:00
|
|
|
#include "log.hh"
|
2017-04-16 08:09:12 +00:00
|
|
|
#include "position.h"
|
2017-04-09 19:38:52 +00:00
|
|
|
|
2018-01-14 09:33:12 +00:00
|
|
|
#include <algorithm>
|
2017-04-17 20:40:50 +00:00
|
|
|
#include <climits>
|
2018-01-14 22:24:47 +00:00
|
|
|
#include <numeric>
|
2018-04-08 00:10:54 +00:00
|
|
|
#include <sstream>
|
2017-04-17 20:40:50 +00:00
|
|
|
|
2017-04-09 19:38:52 +00:00
|
|
|
namespace {
|
|
|
|
|
2018-01-14 23:31:26 +00:00
|
|
|
// When finding a best match of buffer line and index line, limit the max edit
|
|
|
|
// distance.
|
|
|
|
constexpr int kMaxDiff = 20;
|
|
|
|
// Don't align index line to buffer line if one of the lengths is larger than
|
|
|
|
// |kMaxColumnAlignSize|.
|
|
|
|
constexpr int kMaxColumnAlignSize = 200;
|
2018-01-14 09:33:12 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
lsPosition GetPositionForOffset(const std::string &content, int offset) {
|
2017-05-15 07:28:53 +00:00
|
|
|
if (offset >= content.size())
|
2017-05-21 23:22:00 +00:00
|
|
|
offset = (int)content.size() - 1;
|
2017-05-15 07:28:53 +00:00
|
|
|
|
2018-04-16 19:36:02 +00:00
|
|
|
int line = 0, col = 0;
|
2017-05-15 07:28:53 +00:00
|
|
|
int i = 0;
|
2018-04-16 19:36:02 +00:00
|
|
|
for (; i < offset; i++) {
|
|
|
|
if (content[i] == '\n')
|
|
|
|
line++, col = 0;
|
|
|
|
else
|
|
|
|
col++;
|
2017-05-15 07:28:53 +00:00
|
|
|
}
|
2018-04-16 19:36:02 +00:00
|
|
|
return {line, col};
|
2017-05-15 07:28:53 +00:00
|
|
|
}
|
2018-01-14 09:33:12 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
std::vector<std::string> ToLines(const std::string &content) {
|
2018-04-08 00:10:54 +00:00
|
|
|
std::vector<std::string> result;
|
|
|
|
std::istringstream lines(content);
|
|
|
|
std::string line;
|
|
|
|
while (getline(lines, line))
|
|
|
|
result.push_back(line);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-01-14 21:24:52 +00:00
|
|
|
// Computes the edit distance of strings [a,a+la) and [b,b+lb) with Eugene W.
|
|
|
|
// Myers' O(ND) diff algorithm.
|
|
|
|
// Costs: insertion=1, deletion=1, no substitution.
|
|
|
|
// If the distance is larger than threshold, returns threshould + 1.
|
2018-08-09 17:08:14 +00:00
|
|
|
int MyersDiff(const char *a, int la, const char *b, int lb, int threshold) {
|
2018-01-14 09:33:12 +00:00
|
|
|
assert(threshold <= kMaxDiff);
|
2018-02-01 00:48:26 +00:00
|
|
|
static int v_static[2 * kMaxColumnAlignSize + 2];
|
2018-01-30 00:27:43 +00:00
|
|
|
const char *ea = a + la, *eb = b + lb;
|
|
|
|
// Strip prefix
|
|
|
|
for (; a < ea && b < eb && *a == *b; a++, b++) {
|
|
|
|
}
|
|
|
|
// Strip suffix
|
|
|
|
for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
|
|
|
|
}
|
|
|
|
la = int(ea - a);
|
|
|
|
lb = int(eb - b);
|
2018-02-01 00:48:26 +00:00
|
|
|
// If the sum of lengths exceeds what we can handle, return a lower bound.
|
|
|
|
if (la + lb > 2 * kMaxColumnAlignSize)
|
|
|
|
return std::min(abs(la - lb), threshold + 1);
|
2018-01-30 00:27:43 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
int *v = v_static + lb;
|
2018-01-30 00:27:43 +00:00
|
|
|
v[1] = 0;
|
|
|
|
for (int di = 0; di <= threshold; di++) {
|
|
|
|
int low = -di + 2 * std::max(0, di - lb),
|
|
|
|
high = di - 2 * std::max(0, di - la);
|
|
|
|
for (int i = low; i <= high; i += 2) {
|
|
|
|
int x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1]
|
|
|
|
: v[i - 1] + 1,
|
2018-01-14 09:33:12 +00:00
|
|
|
y = x - i;
|
2018-01-30 00:27:43 +00:00
|
|
|
while (x < la && y < lb && a[x] == b[y])
|
|
|
|
x++, y++;
|
|
|
|
v[i] = x;
|
|
|
|
if (x == la && y == lb)
|
2018-01-14 09:33:12 +00:00
|
|
|
return di;
|
2018-01-30 00:27:43 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-14 09:33:12 +00:00
|
|
|
return threshold + 1;
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
int MyersDiff(const std::string &a, const std::string &b, int threshold) {
|
2018-01-14 09:33:12 +00:00
|
|
|
return MyersDiff(a.data(), a.size(), b.data(), b.size(), threshold);
|
|
|
|
}
|
|
|
|
|
2018-01-14 23:31:26 +00:00
|
|
|
// Computes edit distance with O(N*M) Needleman-Wunsch algorithm
|
2018-01-14 22:24:47 +00:00
|
|
|
// and returns a distance vector where d[i] = cost of aligning a to b[0,i).
|
|
|
|
//
|
|
|
|
// Myers' diff algorithm is used to find best matching line while this one is
|
|
|
|
// used to align a single column because Myers' needs some twiddling to return
|
|
|
|
// distance vector.
|
2018-01-14 23:31:26 +00:00
|
|
|
std::vector<int> EditDistanceVector(std::string a, std::string b) {
|
2018-01-14 22:24:47 +00:00
|
|
|
std::vector<int> d(b.size() + 1);
|
|
|
|
std::iota(d.begin(), d.end(), 0);
|
|
|
|
for (int i = 0; i < (int)a.size(); i++) {
|
|
|
|
int ul = d[0];
|
|
|
|
d[0] = i + 1;
|
|
|
|
for (int j = 0; j < (int)b.size(); j++) {
|
|
|
|
int t = d[j + 1];
|
2018-01-14 23:31:26 +00:00
|
|
|
d[j + 1] = a[i] == b[j] ? ul : std::min(d[j], d[j + 1]) + 1;
|
2018-01-14 22:24:47 +00:00
|
|
|
ul = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2018-01-14 23:31:26 +00:00
|
|
|
// Find matching position of |a[column]| in |b|.
|
|
|
|
// This is actually a single step of Hirschberg's sequence alignment algorithm.
|
2018-08-09 17:08:14 +00:00
|
|
|
int AlignColumn(const std::string &a, int column, std::string b, bool is_end) {
|
2018-01-14 23:31:26 +00:00
|
|
|
int head = 0, tail = 0;
|
|
|
|
while (head < (int)a.size() && head < (int)b.size() && a[head] == b[head])
|
|
|
|
head++;
|
|
|
|
while (tail < (int)a.size() && tail < (int)b.size() &&
|
|
|
|
a[a.size() - 1 - tail] == b[b.size() - 1 - tail])
|
|
|
|
tail++;
|
|
|
|
if (column < head)
|
|
|
|
return column;
|
2018-01-15 01:16:24 +00:00
|
|
|
if ((int)a.size() - tail < column)
|
2018-01-14 23:31:26 +00:00
|
|
|
return column + b.size() - a.size();
|
|
|
|
if (std::max(a.size(), b.size()) - head - tail >= kMaxColumnAlignSize)
|
|
|
|
return std::min(column, (int)b.size());
|
|
|
|
|
2018-01-15 01:16:24 +00:00
|
|
|
// b[head, b.size() - tail)
|
2018-01-14 23:31:26 +00:00
|
|
|
b = b.substr(head, b.size() - tail - head);
|
|
|
|
|
|
|
|
// left[i] = cost of aligning a[head, column) to b[head, head + i)
|
|
|
|
std::vector<int> left = EditDistanceVector(a.substr(head, column - head), b);
|
|
|
|
|
|
|
|
// right[i] = cost of aligning a[column, a.size() - tail) to b[head + i,
|
|
|
|
// b.size() - tail)
|
|
|
|
std::string a_rev = a.substr(column, a.size() - tail - column);
|
|
|
|
std::reverse(a_rev.begin(), a_rev.end());
|
|
|
|
std::reverse(b.begin(), b.end());
|
|
|
|
std::vector<int> right = EditDistanceVector(a_rev, b);
|
|
|
|
std::reverse(right.begin(), right.end());
|
|
|
|
|
|
|
|
int best = 0, best_cost = INT_MAX;
|
|
|
|
for (size_t i = 0; i < left.size(); i++) {
|
|
|
|
int cost = left[i] + right[i];
|
2018-01-15 01:16:24 +00:00
|
|
|
if (is_end ? cost < best_cost : cost <= best_cost) {
|
2018-01-14 23:31:26 +00:00
|
|
|
best_cost = cost;
|
|
|
|
best = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return head + best;
|
|
|
|
}
|
|
|
|
|
2018-01-14 21:24:52 +00:00
|
|
|
// Find matching buffer line of index_lines[line].
|
|
|
|
// By symmetry, this can also be used to find matching index line of a buffer
|
|
|
|
// line.
|
2018-08-09 17:08:14 +00:00
|
|
|
std::optional<int>
|
|
|
|
FindMatchingLine(const std::vector<std::string> &index_lines,
|
|
|
|
const std::vector<int> &index_to_buffer, int line, int *column,
|
|
|
|
const std::vector<std::string> &buffer_lines, bool is_end) {
|
2018-01-14 21:24:52 +00:00
|
|
|
// If this is a confident mapping, returns.
|
2018-01-14 23:31:26 +00:00
|
|
|
if (index_to_buffer[line] >= 0) {
|
|
|
|
int ret = index_to_buffer[line];
|
|
|
|
if (column)
|
2018-01-15 01:16:24 +00:00
|
|
|
*column =
|
|
|
|
AlignColumn(index_lines[line], *column, buffer_lines[ret], is_end);
|
2018-01-14 23:31:26 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2018-01-14 21:24:52 +00:00
|
|
|
|
|
|
|
// Find the nearest two confident lines above and below.
|
2018-01-14 18:50:54 +00:00
|
|
|
int up = line, down = line;
|
2018-01-30 00:27:43 +00:00
|
|
|
while (--up >= 0 && index_to_buffer[up] < 0) {
|
|
|
|
}
|
|
|
|
while (++down < int(index_to_buffer.size()) && index_to_buffer[down] < 0) {
|
|
|
|
}
|
2018-01-14 18:50:54 +00:00
|
|
|
up = up < 0 ? 0 : index_to_buffer[up];
|
|
|
|
down = down >= int(index_to_buffer.size()) ? int(buffer_lines.size()) - 1
|
|
|
|
: index_to_buffer[down];
|
|
|
|
if (up > down)
|
2018-03-31 03:16:33 +00:00
|
|
|
return std::nullopt;
|
2018-01-14 21:24:52 +00:00
|
|
|
|
|
|
|
// Search for lines [up,down] and use Myers's diff algorithm to find the best
|
|
|
|
// match (least edit distance).
|
2018-01-14 18:50:54 +00:00
|
|
|
int best = up, best_dist = kMaxDiff + 1;
|
2018-08-09 17:08:14 +00:00
|
|
|
const std::string &needle = index_lines[line];
|
2018-01-14 18:50:54 +00:00
|
|
|
for (int i = up; i <= down; i++) {
|
|
|
|
int dist = MyersDiff(needle, buffer_lines[i], kMaxDiff);
|
|
|
|
if (dist < best_dist) {
|
|
|
|
best_dist = dist;
|
|
|
|
best = i;
|
|
|
|
}
|
|
|
|
}
|
2018-01-14 23:31:26 +00:00
|
|
|
if (column)
|
2018-01-15 01:16:24 +00:00
|
|
|
*column =
|
|
|
|
AlignColumn(index_lines[line], *column, buffer_lines[best], is_end);
|
2018-01-14 19:39:29 +00:00
|
|
|
return best;
|
2018-01-14 18:50:54 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
} // namespace
|
2017-05-29 21:18:35 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile::WorkingFile(const std::string &filename,
|
|
|
|
const std::string &buffer_content)
|
2017-04-16 08:09:12 +00:00
|
|
|
: filename(filename), buffer_content(buffer_content) {
|
|
|
|
OnBufferContentUpdated();
|
2017-04-21 06:32:18 +00:00
|
|
|
|
|
|
|
// SetIndexContent gets called when the file is opened.
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
void WorkingFile::SetIndexContent(const std::string &index_content) {
|
2018-03-31 17:37:03 +00:00
|
|
|
index_lines = ToLines(index_content);
|
2017-04-19 07:52:48 +00:00
|
|
|
|
2018-01-13 19:39:06 +00:00
|
|
|
index_to_buffer.clear();
|
|
|
|
buffer_to_index.clear();
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2017-04-16 08:09:12 +00:00
|
|
|
void WorkingFile::OnBufferContentUpdated() {
|
2018-03-31 17:37:03 +00:00
|
|
|
buffer_lines = ToLines(buffer_content);
|
2017-04-09 19:38:52 +00:00
|
|
|
|
2018-01-13 19:39:06 +00:00
|
|
|
index_to_buffer.clear();
|
|
|
|
buffer_to_index.clear();
|
2017-04-09 19:38:52 +00:00
|
|
|
}
|
|
|
|
|
2018-01-14 21:24:52 +00:00
|
|
|
// Variant of Paul Heckel's diff algorithm to compute |index_to_buffer| and
|
|
|
|
// |buffer_to_index|.
|
|
|
|
// The core idea is that if a line is unique in both index and buffer,
|
|
|
|
// we are confident that the line appeared in index maps to the one appeared in
|
|
|
|
// buffer. And then using them as start points to extend upwards and downwards
|
|
|
|
// to align other identical lines (but not unique).
|
2018-01-13 19:39:06 +00:00
|
|
|
void WorkingFile::ComputeLineMapping() {
|
|
|
|
std::unordered_map<uint64_t, int> hash_to_unique;
|
2018-01-14 21:43:04 +00:00
|
|
|
std::vector<uint64_t> index_hashes(index_lines.size());
|
|
|
|
std::vector<uint64_t> buffer_hashes(buffer_lines.size());
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer.resize(index_lines.size());
|
|
|
|
buffer_to_index.resize(buffer_lines.size());
|
2018-01-30 00:27:43 +00:00
|
|
|
hash_to_unique.reserve(
|
|
|
|
std::max(index_to_buffer.size(), buffer_to_index.size()));
|
2018-01-14 17:49:09 +00:00
|
|
|
|
2018-01-14 21:43:04 +00:00
|
|
|
// For index line i, set index_to_buffer[i] to -1 if line i is duplicated.
|
2018-01-13 19:39:06 +00:00
|
|
|
int i = 0;
|
2018-08-09 17:08:14 +00:00
|
|
|
for (auto &line : index_lines) {
|
2018-04-08 17:32:08 +00:00
|
|
|
uint64_t h = HashUsr(line);
|
2018-01-13 19:39:06 +00:00
|
|
|
auto it = hash_to_unique.find(h);
|
|
|
|
if (it == hash_to_unique.end()) {
|
|
|
|
hash_to_unique[h] = i;
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer[i] = i;
|
2018-01-13 19:39:06 +00:00
|
|
|
} else {
|
|
|
|
if (it->second >= 0)
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer[it->second] = -1;
|
|
|
|
index_to_buffer[i] = it->second = -1;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
|
|
|
index_hashes[i++] = h;
|
|
|
|
}
|
|
|
|
|
2018-01-14 21:43:04 +00:00
|
|
|
// For buffer line i, set buffer_to_index[i] to -1 if line i is duplicated.
|
2018-01-13 19:39:06 +00:00
|
|
|
i = 0;
|
|
|
|
hash_to_unique.clear();
|
2018-08-09 17:08:14 +00:00
|
|
|
for (auto &line : buffer_lines) {
|
2018-04-08 17:32:08 +00:00
|
|
|
uint64_t h = HashUsr(line);
|
2018-01-13 19:39:06 +00:00
|
|
|
auto it = hash_to_unique.find(h);
|
|
|
|
if (it == hash_to_unique.end()) {
|
|
|
|
hash_to_unique[h] = i;
|
2018-01-14 21:24:52 +00:00
|
|
|
buffer_to_index[i] = i;
|
2018-01-13 19:39:06 +00:00
|
|
|
} else {
|
|
|
|
if (it->second >= 0)
|
2018-01-14 21:24:52 +00:00
|
|
|
buffer_to_index[it->second] = -1;
|
|
|
|
buffer_to_index[i] = it->second = -1;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
|
|
|
buffer_hashes[i++] = h;
|
|
|
|
}
|
|
|
|
|
2018-01-30 00:27:43 +00:00
|
|
|
// If index line i is the identical to buffer line j, and they are both
|
|
|
|
// unique, align them by pointing from_index[i] to j.
|
2018-01-13 19:39:06 +00:00
|
|
|
i = 0;
|
|
|
|
for (auto h : index_hashes) {
|
2018-01-14 21:24:52 +00:00
|
|
|
if (index_to_buffer[i] >= 0) {
|
2018-01-13 19:39:06 +00:00
|
|
|
auto it = hash_to_unique.find(h);
|
2018-01-14 09:33:12 +00:00
|
|
|
if (it != hash_to_unique.end() && it->second >= 0 &&
|
2018-01-14 21:24:52 +00:00
|
|
|
buffer_to_index[it->second] >= 0)
|
|
|
|
index_to_buffer[i] = it->second;
|
2018-01-14 18:50:54 +00:00
|
|
|
else
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer[i] = -1;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
2018-01-14 17:49:09 +00:00
|
|
|
// Starting at unique lines, extend upwards and downwards.
|
2018-01-13 19:39:06 +00:00
|
|
|
for (i = 0; i < (int)index_hashes.size() - 1; i++) {
|
2018-01-14 21:24:52 +00:00
|
|
|
int j = index_to_buffer[i];
|
2018-01-13 19:39:06 +00:00
|
|
|
if (0 <= j && j + 1 < buffer_hashes.size() &&
|
2018-01-14 09:49:41 +00:00
|
|
|
index_hashes[i + 1] == buffer_hashes[j + 1])
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer[i + 1] = j + 1;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
2018-01-30 00:27:43 +00:00
|
|
|
for (i = (int)index_hashes.size(); --i > 0;) {
|
2018-01-14 21:24:52 +00:00
|
|
|
int j = index_to_buffer[i];
|
2018-01-14 09:49:41 +00:00
|
|
|
if (0 < j && index_hashes[i - 1] == buffer_hashes[j - 1])
|
2018-01-14 21:24:52 +00:00
|
|
|
index_to_buffer[i - 1] = j - 1;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
2018-01-14 09:49:41 +00:00
|
|
|
|
2018-01-14 21:43:04 +00:00
|
|
|
// |buffer_to_index| is a inverse mapping of |index_to_buffer|.
|
2018-01-14 21:24:52 +00:00
|
|
|
std::fill(buffer_to_index.begin(), buffer_to_index.end(), -1);
|
2018-01-14 09:49:41 +00:00
|
|
|
for (i = 0; i < (int)index_hashes.size(); i++)
|
2018-01-14 21:24:52 +00:00
|
|
|
if (index_to_buffer[i] >= 0)
|
|
|
|
buffer_to_index[index_to_buffer[i]] = i;
|
2018-01-13 19:39:06 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
std::optional<int> WorkingFile::GetBufferPosFromIndexPos(int line, int *column,
|
|
|
|
bool is_end) {
|
2018-01-14 19:39:29 +00:00
|
|
|
if (line < 0 || line >= (int)index_lines.size()) {
|
2018-05-27 19:24:56 +00:00
|
|
|
LOG_S(WARNING) << "bad index_line (got " << line << ", expected [0, "
|
|
|
|
<< index_lines.size() << ")) in " << filename;
|
2018-03-31 03:16:33 +00:00
|
|
|
return std::nullopt;
|
2017-05-07 05:34:43 +00:00
|
|
|
}
|
2017-04-16 08:09:12 +00:00
|
|
|
|
2018-01-14 09:33:12 +00:00
|
|
|
if (index_to_buffer.empty())
|
|
|
|
ComputeLineMapping();
|
2018-01-15 01:16:24 +00:00
|
|
|
return FindMatchingLine(index_lines, index_to_buffer, line, column,
|
|
|
|
buffer_lines, is_end);
|
2017-04-09 19:38:52 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
std::optional<int> WorkingFile::GetIndexPosFromBufferPos(int line, int *column,
|
|
|
|
bool is_end) {
|
2017-04-19 07:52:48 +00:00
|
|
|
// See GetBufferLineFromIndexLine for additional comments.
|
2018-01-20 07:23:50 +00:00
|
|
|
if (line < 0 || line >= (int)buffer_lines.size())
|
2018-03-31 03:16:33 +00:00
|
|
|
return std::nullopt;
|
2017-04-19 07:52:48 +00:00
|
|
|
|
2018-01-14 09:33:12 +00:00
|
|
|
if (buffer_to_index.empty())
|
|
|
|
ComputeLineMapping();
|
2018-01-15 01:16:24 +00:00
|
|
|
return FindMatchingLine(buffer_lines, buffer_to_index, line, column,
|
|
|
|
index_lines, is_end);
|
2017-05-21 04:30:59 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:14:57 +00:00
|
|
|
std::string WorkingFile::FindClosestCallNameInBuffer(
|
2018-08-09 17:08:14 +00:00
|
|
|
lsPosition position, int *active_parameter,
|
|
|
|
lsPosition *completion_position) const {
|
2017-05-15 07:28:53 +00:00
|
|
|
*active_parameter = 0;
|
|
|
|
|
|
|
|
int offset = GetOffsetForPosition(position, buffer_content);
|
|
|
|
|
|
|
|
// If vscode auto-inserts closing ')' we will begin on ')' token in foo()
|
|
|
|
// which will make the below algorithm think it's a nested call.
|
|
|
|
if (offset > 0 && buffer_content[offset] == ')')
|
|
|
|
--offset;
|
|
|
|
|
|
|
|
// Scan back out of call context.
|
|
|
|
int balance = 0;
|
|
|
|
while (offset > 0) {
|
|
|
|
char c = buffer_content[offset];
|
2017-09-22 01:14:57 +00:00
|
|
|
if (c == ')')
|
|
|
|
++balance;
|
|
|
|
else if (c == '(')
|
|
|
|
--balance;
|
2017-05-15 07:28:53 +00:00
|
|
|
|
|
|
|
if (balance == 0 && c == ',')
|
|
|
|
*active_parameter += 1;
|
|
|
|
|
|
|
|
--offset;
|
|
|
|
|
|
|
|
if (balance == -1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offset < 0)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
// Scan back entire identifier.
|
|
|
|
int start_offset = offset;
|
|
|
|
while (offset > 0) {
|
|
|
|
char c = buffer_content[offset - 1];
|
|
|
|
if (isalnum(c) == false && c != '_')
|
|
|
|
break;
|
|
|
|
--offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (completion_position)
|
|
|
|
*completion_position = GetPositionForOffset(buffer_content, offset);
|
|
|
|
|
|
|
|
return buffer_content.substr(offset, start_offset - offset + 1);
|
|
|
|
}
|
|
|
|
|
2018-09-11 05:37:01 +00:00
|
|
|
lsPosition
|
|
|
|
WorkingFile::FindStableCompletionSource(lsPosition position,
|
|
|
|
std::string *existing_completion,
|
|
|
|
lsPosition *replace_end_pos) const {
|
2017-06-16 02:28:49 +00:00
|
|
|
int start_offset = GetOffsetForPosition(position, buffer_content);
|
|
|
|
int offset = start_offset;
|
2017-05-20 08:07:29 +00:00
|
|
|
|
|
|
|
while (offset > 0) {
|
|
|
|
char c = buffer_content[offset - 1];
|
2018-09-11 05:37:01 +00:00
|
|
|
if (!isalnum(c) && c != '_')
|
2017-05-20 08:07:29 +00:00
|
|
|
break;
|
|
|
|
--offset;
|
|
|
|
}
|
|
|
|
|
2018-04-14 16:52:17 +00:00
|
|
|
*replace_end_pos = position;
|
|
|
|
for (int i = start_offset; i < buffer_content.size(); i++) {
|
|
|
|
char c = buffer_content[i];
|
2018-08-09 17:08:14 +00:00
|
|
|
if (!isalnum(c) && c != '_')
|
|
|
|
break;
|
2018-04-14 16:52:17 +00:00
|
|
|
// We know that replace_end_pos and position are on the same line.
|
|
|
|
replace_end_pos->character++;
|
|
|
|
}
|
|
|
|
|
2017-06-16 02:28:49 +00:00
|
|
|
*existing_completion = buffer_content.substr(offset, start_offset - offset);
|
2017-05-20 08:07:29 +00:00
|
|
|
return GetPositionForOffset(buffer_content, offset);
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile *WorkingFiles::GetFileByFilename(const std::string &filename) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
return GetFileByFilenameNoLock(filename);
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile *
|
|
|
|
WorkingFiles::GetFileByFilenameNoLock(const std::string &filename) {
|
|
|
|
for (auto &file : files) {
|
2017-03-26 21:40:34 +00:00
|
|
|
if (file->filename == filename)
|
|
|
|
return file.get();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-08-28 05:42:40 +00:00
|
|
|
std::string WorkingFiles::GetContent(const std::string &filename) {
|
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
for (auto &file : files)
|
|
|
|
if (file->filename == filename)
|
|
|
|
return file->buffer_content;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
void WorkingFiles::DoAction(const std::function<void()> &action) {
|
2017-06-14 06:29:41 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
action();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorkingFiles::DoActionOnFile(
|
2018-08-09 17:08:14 +00:00
|
|
|
const std::string &filename,
|
|
|
|
const std::function<void(WorkingFile *file)> &action) {
|
2017-06-14 06:29:41 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile *file = GetFileByFilenameNoLock(filename);
|
2017-06-14 06:29:41 +00:00
|
|
|
action(file);
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile *WorkingFiles::OnOpen(const lsTextDocumentItem &open) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
|
2017-12-06 04:39:44 +00:00
|
|
|
std::string filename = open.uri.GetPath();
|
|
|
|
std::string content = open.text;
|
2017-03-26 21:40:34 +00:00
|
|
|
|
|
|
|
// The file may already be open.
|
2018-08-09 17:08:14 +00:00
|
|
|
if (WorkingFile *file = GetFileByFilenameNoLock(filename)) {
|
2017-12-06 04:39:44 +00:00
|
|
|
file->version = open.version;
|
2017-04-16 08:09:12 +00:00
|
|
|
file->buffer_content = content;
|
|
|
|
file->OnBufferContentUpdated();
|
2017-04-21 06:32:18 +00:00
|
|
|
return file;
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2018-03-10 23:40:27 +00:00
|
|
|
files.push_back(std::make_unique<WorkingFile>(filename, content));
|
2017-04-21 06:32:18 +00:00
|
|
|
return files[files.size() - 1].get();
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams &change) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
|
2017-03-26 21:40:34 +00:00
|
|
|
std::string filename = change.textDocument.uri.GetPath();
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFile *file = GetFileByFilenameNoLock(filename);
|
2017-03-26 21:40:34 +00:00
|
|
|
if (!file) {
|
2017-12-02 01:04:39 +00:00
|
|
|
LOG_S(WARNING) << "Could not change " << filename
|
|
|
|
<< " because it was not open";
|
2017-03-26 21:40:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-10 07:57:33 +00:00
|
|
|
// version: number | null
|
2018-04-16 19:36:02 +00:00
|
|
|
if (change.textDocument.version)
|
|
|
|
file->version = *change.textDocument.version;
|
2017-03-26 21:40:34 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
for (const lsTextDocumentContentChangeEvent &diff : change.contentChanges) {
|
2017-11-20 18:57:01 +00:00
|
|
|
// Per the spec replace everything if the rangeLength and range are not set.
|
|
|
|
// See https://github.com/Microsoft/language-server-protocol/issues/9.
|
2017-12-25 08:58:26 +00:00
|
|
|
if (!diff.range) {
|
2017-04-16 08:09:12 +00:00
|
|
|
file->buffer_content = diff.text;
|
|
|
|
file->OnBufferContentUpdated();
|
2017-09-22 01:14:57 +00:00
|
|
|
} else {
|
2017-12-01 17:50:39 +00:00
|
|
|
int start_offset =
|
2017-12-25 08:58:26 +00:00
|
|
|
GetOffsetForPosition(diff.range->start, file->buffer_content);
|
2018-01-13 18:43:37 +00:00
|
|
|
// Ignore TextDocumentContentChangeEvent.rangeLength which causes trouble
|
|
|
|
// when UTF-16 surrogate pairs are used.
|
2018-01-30 00:27:43 +00:00
|
|
|
int end_offset =
|
|
|
|
GetOffsetForPosition(diff.range->end, file->buffer_content);
|
2017-12-27 15:53:35 +00:00
|
|
|
file->buffer_content.replace(file->buffer_content.begin() + start_offset,
|
|
|
|
file->buffer_content.begin() + end_offset,
|
|
|
|
diff.text);
|
2017-04-16 08:09:12 +00:00
|
|
|
file->OnBufferContentUpdated();
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
void WorkingFiles::OnClose(const lsTextDocumentIdentifier &close) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
|
2017-12-06 04:39:44 +00:00
|
|
|
std::string filename = close.uri.GetPath();
|
2017-03-26 21:40:34 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < files.size(); ++i) {
|
|
|
|
if (files[i]->filename == filename) {
|
|
|
|
files.erase(files.begin() + i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 01:04:39 +00:00
|
|
|
LOG_S(WARNING) << "Could not close " << filename
|
|
|
|
<< " because it was not open";
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
WorkingFiles::Snapshot
|
|
|
|
WorkingFiles::AsSnapshot(const std::vector<std::string> &filter_paths) {
|
2018-01-06 05:45:20 +00:00
|
|
|
std::lock_guard<std::mutex> lock(files_mutex);
|
|
|
|
|
2018-01-12 17:37:33 +00:00
|
|
|
Snapshot result;
|
2018-01-06 05:45:20 +00:00
|
|
|
result.files.reserve(files.size());
|
2018-08-09 17:08:14 +00:00
|
|
|
for (const auto &file : files) {
|
2018-01-12 17:37:33 +00:00
|
|
|
if (filter_paths.empty() || FindAnyPartial(file->filename, filter_paths))
|
|
|
|
result.files.push_back({file->filename, file->buffer_content});
|
|
|
|
}
|
2017-03-26 21:40:34 +00:00
|
|
|
return result;
|
2017-04-17 20:40:50 +00:00
|
|
|
}
|
2018-06-01 03:06:09 +00:00
|
|
|
|
|
|
|
// VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent
|
|
|
|
// text documents.
|
|
|
|
// We use a UTF-8 iterator to approximate UTF-16 in the specification (weird).
|
|
|
|
// This is good enough and fails only for UTF-16 surrogate pairs.
|
|
|
|
int GetOffsetForPosition(lsPosition position, std::string_view content) {
|
|
|
|
size_t i = 0;
|
|
|
|
for (; position.line > 0 && i < content.size(); i++)
|
|
|
|
if (content[i] == '\n')
|
|
|
|
position.line--;
|
|
|
|
for (; position.character > 0 && i < content.size(); position.character--)
|
|
|
|
if (uint8_t(content[i++]) >= 128) {
|
|
|
|
// Skip 0b10xxxxxx
|
|
|
|
while (i < content.size() && uint8_t(content[i]) >= 128 &&
|
|
|
|
uint8_t(content[i]) < 192)
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return int(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string_view LexIdentifierAroundPos(lsPosition position,
|
|
|
|
std::string_view content) {
|
|
|
|
int start = GetOffsetForPosition(position, content);
|
|
|
|
int end = start + 1;
|
|
|
|
char c;
|
|
|
|
|
|
|
|
// We search for :: before the cursor but not after to get the qualifier.
|
|
|
|
for (; start > 0; start--) {
|
|
|
|
c = content[start - 1];
|
|
|
|
if (isalnum(c) || c == '_')
|
|
|
|
;
|
|
|
|
else if (c == ':' && start > 1 && content[start - 2] == ':')
|
|
|
|
start--;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; end < (int)content.size(); end++)
|
|
|
|
if (c = content[end], !(isalnum(c) || c == '_'))
|
|
|
|
break;
|
|
|
|
|
|
|
|
return content.substr(start, end - start);
|
|
|
|
}
|