Skip to content

Commit e68c39f

Browse files
committed
keywords highlighting - done
Command: Toggle keyword highlighting has been added
1 parent 485113e commit e68c39f

File tree

4 files changed

+129
-127
lines changed

4 files changed

+129
-127
lines changed

data.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2-
"highlightColorOccurrences": "#d888d6",
2+
"highlightColorOccurrences": "#FFFF00",
3+
"highlightColorKeywords": "#bdfc64",
34
"occuraPluginEnabled": true,
45
"occuraPluginEnabledHotKey": "",
56
"statusBarOccurrencesNumberEnabled": true

main.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Plugin, MarkdownView, Notice, WorkspaceLeaf, setIcon, Keymap, setTooltip} from 'obsidian';
1+
import {Plugin, MarkdownView, Notice, WorkspaceLeaf, setIcon, setTooltip} from 'obsidian';
22
import {Compartment} from '@codemirror/state';
33
import {EditorView} from '@codemirror/view';
44
import {OccuraPluginSettingTab, OccuraPluginSettings, DEFAULT_SETTINGS} from 'src/settings'
@@ -43,6 +43,14 @@ export default class OccuraPlugin extends Plugin {
4343
}
4444
});
4545

46+
this.addCommand({
47+
id: 'toggle-keyword-highlighting',
48+
name: 'Toggle keyword highlighting',
49+
callback: () => {
50+
this.toggleKeywordHighlighting();
51+
}
52+
});
53+
4654
this.addCommand({
4755
id: 'set-permanent-highlight-occurrences',
4856
name: 'Set permanently highlight for occurrences',
@@ -154,6 +162,15 @@ export default class OccuraPlugin extends Plugin {
154162
// Optional: Show a notice
155163
//new Notice(`Occura ${this.settings.occuraPluginEnabled ? 'enabled' : 'disabled'}`);
156164
}
165+
// Toggle keywords highlighting functionality
166+
toggleKeywordHighlighting() {
167+
this.settings.autoKeywordsHighlightEnabled = !this.settings.autoKeywordsHighlightEnabled;
168+
this.saveSettings();
169+
// Force the editor to re-render
170+
this.updateEditors();
171+
// Optional: Show a notice
172+
//new Notice(`Keyword highlighting ${this.settings.autoKeywordsHighlightEnabled ? 'enabled' : 'disabled'}`);
173+
}
157174

158175
// Clear selection when clicking outside the editor
159176
private handleDocumentClick(evt: MouseEvent) {
@@ -166,13 +183,16 @@ export default class OccuraPlugin extends Plugin {
166183
}
167184
}
168185

169-
// Method to update the highlight style based on settings
186+
// Method to dynamic update the highlight style based on settings
170187
updateHighlightStyle() {
171188
if (this.styleEl) {
172189
this.styleEl.remove();
173190
}
174191
this.styleEl = document.createElement('style');
175-
this.styleEl.textContent = `.found-occurrence {background-color: ${this.settings.highlightColorOccurrences};}`;
192+
this.styleEl.textContent = `
193+
.found-occurrence {background-color: ${this.settings.highlightColorOccurrences};}
194+
.keyword-occurrence { background-color: ${this.settings.highlightColorKeywords}; }
195+
`;
176196
document.head.appendChild(this.styleEl);
177197
}
178198

@@ -273,9 +293,6 @@ export default class OccuraPlugin extends Plugin {
273293
const icons = document.querySelectorAll('.highlight-toggle-icon');
274294
icons.forEach(icon => icon.remove());
275295
}
276-
277-
278-
279296
}
280297

281298

src/highlighter.ts

Lines changed: 70 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,24 @@
1-
// highlighter.ts
2-
31
import {EditorView, Decoration, DecorationSet, ViewUpdate, ViewPlugin} from '@codemirror/view';
42
import {RangeSetBuilder} from '@codemirror/state';
53
import type OccuraPlugin from 'main';
64
import {MarkdownView, Notice} from "obsidian";
75

8-
// Create a decoration for highlighting
9-
export const highlightDecoration = Decoration.mark({class: 'found-occurrence'});
6+
// Create a decoration for highlighting by select
7+
export const selectedTextDecoration = Decoration.mark({class: 'found-occurrence', priority: 100,});
8+
// Create a decoration for highlighting based on keywords list
9+
export const keywordDecoration = Decoration.mark({class: 'keyword-occurrence', priority: 50,});
1010
let iFoundOccurCount = 0;
1111

12-
13-
/*export function highlightOccurrenceExtension(plugin: OccuraPlugin) {
14-
return ViewPlugin.fromClass(
15-
class {
16-
decorations: DecorationSet;
17-
lastEnabledState: boolean;
18-
19-
constructor(public view: EditorView) {
20-
this.lastEnabledState = plugin.settings.occuraPluginEnabled;
21-
this.decorations = this.createDecorations();
22-
}
23-
24-
update(update: ViewUpdate) {
25-
if (
26-
update.selectionSet ||
27-
update.docChanged ||
28-
update.viewportChanged ||
29-
plugin.settings.occuraPluginEnabled !== this.lastEnabledState
30-
) {
31-
this.decorations = this.createDecorations();
32-
}
33-
}
34-
35-
createDecorations() {
36-
this.lastEnabledState = plugin.settings.occuraPluginEnabled;
37-
38-
if (!plugin.settings.occuraPluginEnabled) {
39-
return Decoration.none;
40-
}
41-
42-
const { state } = this.view;
43-
const selection = state.selection.main;
44-
45-
// Return empty decorations if no selection or selection is empty
46-
if (selection.empty) {
47-
if (plugin.statusBarOccurrencesNumber) {
48-
if (plugin.settings.statusBarOccurrencesNumberEnabled)
49-
plugin.statusBarOccurrencesNumber.setText("");
50-
}
51-
return Decoration.none;
52-
}
53-
54-
const selectedText = state.doc.sliceString(selection.from, selection.to).trim();
55-
56-
// Return empty decorations if selection is whitespace or empty
57-
if (!selectedText || /\s/.test(selectedText)) {
58-
return Decoration.none;
59-
}
60-
61-
iFoundOccurCount = 0;
62-
const builder = new RangeSetBuilder<Decoration>();
63-
const regex = new RegExp(selectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
64-
65-
for (const { from, to } of this.view.visibleRanges) {
66-
const text = state.doc.sliceString(from, to);
67-
let match;
68-
while ((match = regex.exec(text)) !== null) {
69-
const start = from + match.index;
70-
const end = start + match[0].length;
71-
builder.add(start, end, highlightDecoration);
72-
iFoundOccurCount++;
73-
}
74-
}
75-
if (plugin.statusBarOccurrencesNumber) {
76-
if (plugin.settings.statusBarOccurrencesNumberEnabled)
77-
plugin.statusBarOccurrencesNumber.setText(`Occura found: ${selectedText} ` + iFoundOccurCount + ' times');
78-
}
79-
return builder.finish();
80-
}
81-
},
82-
{
83-
decorations: v => v.decorations,
84-
}
85-
);
86-
}*/
87-
8812
export function highlightOccurrenceExtension(plugin: OccuraPlugin) {
8913
return ViewPlugin.fromClass(
9014
class {
9115
decorations: DecorationSet;
92-
lastEnabledState: boolean;
16+
lastOccuraPluginEnabledState: boolean;
17+
lastAutoKeywordsHighlightEnabledState: boolean;
9318

9419
constructor(public view: EditorView) {
95-
this.lastEnabledState = plugin.settings.occuraPluginEnabled;
20+
this.lastOccuraPluginEnabledState = plugin.settings.occuraPluginEnabled;
21+
this.lastAutoKeywordsHighlightEnabledState = plugin.settings.autoKeywordsHighlightEnabled;
9622
this.decorations = this.createDecorations();
9723
}
9824

@@ -101,88 +27,119 @@ export function highlightOccurrenceExtension(plugin: OccuraPlugin) {
10127
update.selectionSet ||
10228
update.docChanged ||
10329
update.viewportChanged ||
104-
plugin.settings.occuraPluginEnabled !== this.lastEnabledState ||
105-
plugin.settings.autoKeywordsHighlightEnabled !== this.lastEnabledState
30+
plugin.settings.occuraPluginEnabled !== this.lastOccuraPluginEnabledState ||
31+
plugin.settings.autoKeywordsHighlightEnabled !== this.lastAutoKeywordsHighlightEnabledState
10632
) {
10733
this.decorations = this.createDecorations();
10834
}
10935
}
11036

11137
createDecorations() {
112-
this.lastEnabledState = plugin.settings.occuraPluginEnabled;
38+
// Update the last known states
39+
this.lastOccuraPluginEnabledState = plugin.settings.occuraPluginEnabled;
40+
this.lastAutoKeywordsHighlightEnabledState = plugin.settings.autoKeywordsHighlightEnabled;
11341

114-
const { state } = this.view;
115-
const matches: { from: number; to: number }[] = [];
42+
const {state} = this.view;
43+
const matches: { from: number; to: number; decoration: Decoration }[] = [];
11644
iFoundOccurCount = 0;
11745

118-
// If Occura plugin is enabled and there's a selection
46+
// **Handle selected text highlighting**
11947
if (plugin.settings.occuraPluginEnabled) {
12048
const selection = state.selection.main;
12149

12250
if (!selection.empty) {
12351
const selectedText = state.doc.sliceString(selection.from, selection.to).trim();
12452

12553
if (selectedText && !/\s/.test(selectedText)) {
126-
const regex = new RegExp(selectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
54+
const regex = new RegExp(
55+
selectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
56+
'g'
57+
);
12758

128-
for (const { from, to } of this.view.visibleRanges) {
59+
for (const {from, to} of this.view.visibleRanges) {
12960
const text = state.doc.sliceString(from, to);
13061
let match;
13162
while ((match = regex.exec(text)) !== null) {
13263
const start = from + match.index;
13364
const end = start + match[0].length;
134-
matches.push({ from: start, to: end });
65+
matches.push({
66+
from: start,
67+
to: end,
68+
decoration: selectedTextDecoration,
69+
});
13570
iFoundOccurCount++;
13671
}
13772
}
13873

139-
if (plugin.statusBarOccurrencesNumber && plugin.settings.statusBarOccurrencesNumberEnabled) {
74+
if (
75+
plugin.statusBarOccurrencesNumber &&
76+
plugin.settings.statusBarOccurrencesNumberEnabled
77+
) {
14078
plugin.statusBarOccurrencesNumber.setText(
14179
`Occura found: ${selectedText} ` + iFoundOccurCount + ' times'
14280
);
14381
}
82+
} else {
83+
if (
84+
plugin.statusBarOccurrencesNumber &&
85+
plugin.settings.statusBarOccurrencesNumberEnabled
86+
) {
87+
plugin.statusBarOccurrencesNumber.setText('');
88+
}
14489
}
14590
} else {
146-
if (plugin.statusBarOccurrencesNumber && plugin.settings.statusBarOccurrencesNumberEnabled) {
147-
plugin.statusBarOccurrencesNumber.setText("");
91+
if (
92+
plugin.statusBarOccurrencesNumber &&
93+
plugin.settings.statusBarOccurrencesNumberEnabled
94+
) {
95+
plugin.statusBarOccurrencesNumber.setText('');
14896
}
14997
}
15098
}
151-
152-
// If auto keyword highlighting is enabled
153-
if (plugin.settings.autoKeywordsHighlightEnabled && plugin.settings.keywords.length > 0) {
99+
// **Handle keyword highlighting**
100+
if (
101+
plugin.settings.occuraPluginEnabled &&
102+
plugin.settings.autoKeywordsHighlightEnabled &&
103+
plugin.settings.keywords.length > 0
104+
) {
154105
const keywords = plugin.settings.keywords.filter(k => k.trim() !== '');
155106

156107
if (keywords.length > 0) {
108+
// Determine the regex flags based on case sensitivity setting
109+
const regexFlags = plugin.settings.keywordsCaseSensitive ? 'g' : 'gi';
157110
const keywordRegexes = keywords.map(keyword => {
158111
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
159-
return new RegExp(`\\b${escapedKeyword}\\b`, 'g');
112+
return new RegExp(`\\b${escapedKeyword}\\b`, regexFlags);
160113
});
161114

162-
for (const { from, to } of this.view.visibleRanges) {
115+
for (const {from, to} of this.view.visibleRanges) {
163116
const text = state.doc.sliceString(from, to);
164117

165-
keywordRegexes.forEach(regex => {
118+
keywordRegexes.forEach((regex) => {
166119
let match;
167120
while ((match = regex.exec(text)) !== null) {
168121
const start = from + match.index;
169122
const end = start + match[0].length;
170-
matches.push({ from: start, to: end });
123+
matches.push({
124+
from: start,
125+
to: end,
126+
decoration: keywordDecoration,
127+
});
171128
}
172129
});
173130
}
174131
}
175132
}
176133

177-
// Now, sort the matches by their 'from' position
134+
// **Sort the matches by their 'from' position**
178135
matches.sort((a, b) => a.from - b.from);
179136

180-
// Create a RangeSetBuilder
137+
// **Create a RangeSetBuilder**
181138
const builder = new RangeSetBuilder<Decoration>();
182139

183-
// Add the sorted ranges to the builder
140+
// **Add the sorted ranges to the builder**
184141
for (const range of matches) {
185-
builder.add(range.from, range.to, highlightDecoration);
142+
builder.add(range.from, range.to, range.decoration);
186143
}
187144

188145
return builder.finish();
@@ -194,9 +151,8 @@ export function highlightOccurrenceExtension(plugin: OccuraPlugin) {
194151
);
195152
}
196153

197-
198154
//region set/remove permanent highlighting
199-
export function setHighlightOccurrences(context:any) {
155+
export function setHighlightOccurrences(context: any) {
200156
const activeView = context.app.workspace.getActiveViewOfType(MarkdownView);
201157
if (!activeView) {
202158
new Notice('No active editor');
@@ -220,7 +176,7 @@ export function setHighlightOccurrences(context:any) {
220176

221177
let match;
222178
while ((match = regex.exec(docText)) !== null) {
223-
matches.push({ from: match.index, to: match.index + match[0].length });
179+
matches.push({from: match.index, to: match.index + match[0].length});
224180
}
225181

226182
if (matches.length === 0) {
@@ -249,7 +205,7 @@ export function setHighlightOccurrences(context:any) {
249205

250206
new Notice(`Permanently highlighted ${matches.length} for "${selectedText}" occurrences.`);
251207
}
252-
export function removeHighlightOccurrences(context:any) {
208+
export function removeHighlightOccurrences(context: any) {
253209
const activeView = context.app.workspace.getActiveViewOfType(MarkdownView);
254210
if (!activeView) {
255211
new Notice('No active editor');
@@ -274,7 +230,7 @@ export function removeHighlightOccurrences(context:any) {
274230

275231
let match;
276232
while ((match = regex.exec(docText)) !== null) {
277-
matches.push({ from: match.index, to: match.index + match[0].length });
233+
matches.push({from: match.index, to: match.index + match[0].length});
278234
}
279235

280236
if (matches.length === 0) {
@@ -310,7 +266,7 @@ export function removeHighlightOccurrences(context:any) {
310266

311267
//region set/remove tags
312268
//it works for Live and Source mode
313-
export function createTagForOccurrences(context:any) {
269+
export function createTagForOccurrences(context: any) {
314270
const activeView = context.app.workspace.getActiveViewOfType(MarkdownView);
315271
if (!activeView) {
316272
new Notice('No active editor');
@@ -336,7 +292,7 @@ export function createTagForOccurrences(context:any) {
336292

337293
let match;
338294
while ((match = regex.exec(docText)) !== null) {
339-
matches.push({ from: match.index, to: match.index + match[0].length });
295+
matches.push({from: match.index, to: match.index + match[0].length});
340296
}
341297

342298
if (matches.length === 0) {
@@ -357,7 +313,7 @@ export function createTagForOccurrences(context:any) {
357313

358314
new Notice(`Tagged ${matches.length} occurrences of "${selectedText}".`);
359315
}
360-
export function removeTagFromOccurrences(context:any) {
316+
export function removeTagFromOccurrences(context: any) {
361317
//
362318
const activeView = context.app.workspace.getActiveViewOfType(MarkdownView);
363319
if (!activeView) {
@@ -384,7 +340,7 @@ export function removeTagFromOccurrences(context:any) {
384340

385341
let match;
386342
while ((match = regex.exec(docText)) !== null) {
387-
matches.push({ from: match.index, to: match.index + match[0].length });
343+
matches.push({from: match.index, to: match.index + match[0].length});
388344
}
389345

390346
if (matches.length === 0) {

0 commit comments

Comments
 (0)