Show read/write kind and let the panel filter on it
The reference request carries no read/write information, so derive it: - Classify every reference syntactically first — assignment and compound assignment, ++/--, and ref/out arguments are writes, everything else is a read. The suffix test only looks past the end of the reference, so the `=` in `previous = x` does not make the read of `x` look like a write. - Then ask textDocument/documentHighlight per file, whose Read/Write kinds override the syntactic answer. Plain Text highlights carry no kind and leave it standing, so servers without the feature still get a sensible column. Files have to be opened as text documents for the server to answer, so results spanning more than 60 files skip this step. In the panel: a sortable Kind column with read/write badges, All / Reads / Writes buttons, the kind included in the text filter, and a write count in the summary line. The kind filter is persisted with the rest of the view state. Line and Kind columns carry minimum widths that fit their own headers, which Line previously did not. Verified against DotRush on a field that is both read and written: the assignment and the `ref` argument classify as writes, the subscript, the comparison and the right-hand-side use as reads. The editor view does not mark writes yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+48
-5
@@ -13,13 +13,21 @@
|
||||
|
||||
// `share` is the fraction of the panel a column gets before anyone drags it.
|
||||
const COLUMNS = [
|
||||
{ key: 'code', label: 'Code', share: 0.40, min: 120, align: 'left' },
|
||||
{ key: 'file', label: 'File', share: 0.20, min: 80, align: 'left' },
|
||||
{ key: 'line', label: 'Line', share: 0.06, min: 44, align: 'right' },
|
||||
{ key: 'code', label: 'Code', share: 0.36, min: 120, align: 'left' },
|
||||
{ key: 'file', label: 'File', share: 0.18, min: 80, align: 'left' },
|
||||
// Minimums here are what the column's own header needs, not just its content.
|
||||
{ key: 'line', label: 'Line', share: 0.05, min: 58, align: 'right' },
|
||||
{ key: 'kind', label: 'Kind', share: 0.07, min: 62, align: 'left' },
|
||||
{ key: 'project', label: 'Project', share: 0.14, min: 60, align: 'left' },
|
||||
{ key: 'member', label: 'Containing member', share: 0.20, min: 80, align: 'left' },
|
||||
];
|
||||
|
||||
const KIND_FILTERS = [
|
||||
{ value: 'all', label: 'All', title: 'Show reads and writes' },
|
||||
{ value: 'read', label: 'Reads', title: 'Show only references that read the symbol' },
|
||||
{ value: 'write', label: 'Writes', title: 'Show only references that write the symbol' },
|
||||
];
|
||||
|
||||
/** @type {{symbol: string, languageId: string, rows: any[], fileCount: number}} */
|
||||
let data = { symbol: '', languageId: '', rows: [], fileCount: 0 };
|
||||
|
||||
@@ -29,6 +37,7 @@
|
||||
sortDir: restored.sortDir === 'desc' ? 'desc' : 'asc',
|
||||
group: restored.group !== false,
|
||||
filter: restored.filter || '',
|
||||
kind: KIND_FILTERS.some(k => k.value === restored.kind) ? restored.kind : 'all',
|
||||
widths: Object.assign({}, restored.widths),
|
||||
collapsed: Object.assign({}, restored.collapsed),
|
||||
selectedId: restored.selectedId,
|
||||
@@ -37,6 +46,7 @@
|
||||
const el = {
|
||||
summary: /** @type {HTMLElement} */ (document.getElementById('summary')),
|
||||
filter: /** @type {HTMLInputElement} */ (document.getElementById('filter')),
|
||||
kindFilter: /** @type {HTMLElement} */ (document.getElementById('kind-filter')),
|
||||
group: /** @type {HTMLButtonElement} */ (document.getElementById('toggle-group')),
|
||||
refresh: /** @type {HTMLButtonElement} */ (document.getElementById('refresh')),
|
||||
table: /** @type {HTMLElement} */ (document.getElementById('table')),
|
||||
@@ -341,10 +351,13 @@
|
||||
}
|
||||
|
||||
function matches(row, needle) {
|
||||
if (state.kind !== 'all' && row.kind !== state.kind) {
|
||||
return false;
|
||||
}
|
||||
if (!needle) {
|
||||
return true;
|
||||
}
|
||||
return (row.code + ' ' + row.relPath + ' ' + row.project + ' ' + row.member)
|
||||
return (row.code + ' ' + row.relPath + ' ' + row.project + ' ' + row.member + ' ' + row.kind)
|
||||
.toLowerCase().indexOf(needle) >= 0;
|
||||
}
|
||||
|
||||
@@ -385,6 +398,14 @@
|
||||
div.appendChild(file);
|
||||
|
||||
div.appendChild(cell('line', String(row.line)));
|
||||
|
||||
const kind = cell('kind');
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'kind-badge kind-' + row.kind;
|
||||
badge.textContent = row.kind;
|
||||
kind.appendChild(badge);
|
||||
div.appendChild(kind);
|
||||
|
||||
div.appendChild(cell('project', row.project));
|
||||
|
||||
const member = cell('member', row.member);
|
||||
@@ -463,6 +484,7 @@
|
||||
|
||||
const shown = rows.length;
|
||||
const total = data.rows.length;
|
||||
const writes = data.writeCount || 0;
|
||||
el.summary.textContent = '';
|
||||
const strong = document.createElement('b');
|
||||
strong.textContent = data.symbol;
|
||||
@@ -470,7 +492,8 @@
|
||||
el.summary.appendChild(document.createTextNode(`${total} reference${total === 1 ? '' : 's'} to `));
|
||||
el.summary.appendChild(strong);
|
||||
el.summary.appendChild(document.createTextNode(
|
||||
` in ${data.fileCount} file${data.fileCount === 1 ? '' : 's'}`));
|
||||
` in ${data.fileCount} file${data.fileCount === 1 ? '' : 's'}` +
|
||||
(writes > 0 ? ` · ${writes} write${writes === 1 ? '' : 's'}` : '')));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -565,6 +588,25 @@
|
||||
renderRows();
|
||||
});
|
||||
|
||||
function buildKindFilter() {
|
||||
el.kindFilter.textContent = '';
|
||||
for (const option of KIND_FILTERS) {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'toolbar-button segment';
|
||||
button.textContent = option.label;
|
||||
button.title = option.title;
|
||||
button.dataset.kind = option.value;
|
||||
button.setAttribute('aria-pressed', String(state.kind === option.value));
|
||||
button.addEventListener('click', () => {
|
||||
state.kind = option.value;
|
||||
saveState();
|
||||
buildKindFilter();
|
||||
renderRows();
|
||||
});
|
||||
el.kindFilter.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
el.group.addEventListener('click', () => {
|
||||
state.group = !state.group;
|
||||
el.group.setAttribute('aria-pressed', String(state.group));
|
||||
@@ -623,6 +665,7 @@
|
||||
|
||||
applyPalette();
|
||||
el.filter.value = state.filter;
|
||||
buildKindFilter();
|
||||
el.group.setAttribute('aria-pressed', String(state.group));
|
||||
el.body.tabIndex = 0;
|
||||
computeAutoWidths();
|
||||
|
||||
Reference in New Issue
Block a user