1- // highlighter.ts
2-
31import { EditorView , Decoration , DecorationSet , ViewUpdate , ViewPlugin } from '@codemirror/view' ;
42import { RangeSetBuilder } from '@codemirror/state' ;
53import type OccuraPlugin from 'main' ;
64import { 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 , } ) ;
1010let 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-
8812export 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