-
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #636 from mindofmatthew/highlight-ids
More work on highlight IDs
- Loading branch information
Showing
17 changed files
with
4,835 additions
and
5,031 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { StateEffect, StateField } from '@codemirror/state'; | ||
import { Decoration, EditorView } from '@codemirror/view'; | ||
|
||
export const setFlash = StateEffect.define(); | ||
export const flashField = StateField.define({ | ||
create() { | ||
return Decoration.none; | ||
}, | ||
update(flash, tr) { | ||
try { | ||
for (let e of tr.effects) { | ||
if (e.is(setFlash)) { | ||
if (e.value && tr.newDoc.length > 0) { | ||
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } }); | ||
flash = Decoration.set([mark.range(0, tr.newDoc.length)]); | ||
} else { | ||
flash = Decoration.set([]); | ||
} | ||
} | ||
} | ||
return flash; | ||
} catch (err) { | ||
console.warn('flash error', err); | ||
return flash; | ||
} | ||
}, | ||
provide: (f) => EditorView.decorations.from(f), | ||
}); | ||
|
||
export const flash = (view, ms = 200) => { | ||
view.dispatch({ effects: setFlash.of(true) }); | ||
setTimeout(() => { | ||
view.dispatch({ effects: setFlash.of(false) }); | ||
}, ms); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { RangeSetBuilder, StateEffect, StateField } from '@codemirror/state'; | ||
import { Decoration, EditorView } from '@codemirror/view'; | ||
|
||
export const setMiniLocations = StateEffect.define(); | ||
export const showMiniLocations = StateEffect.define(); | ||
export const updateMiniLocations = (view, locations) => { | ||
view.dispatch({ effects: setMiniLocations.of(locations) }); | ||
}; | ||
export const highlightMiniLocations = (view, atTime, haps) => { | ||
view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }); | ||
}; | ||
|
||
const miniLocations = StateField.define({ | ||
create() { | ||
return Decoration.none; | ||
}, | ||
update(locations, tr) { | ||
if (tr.docChanged) { | ||
locations = locations.map(tr.changes); | ||
} | ||
|
||
for (let e of tr.effects) { | ||
if (e.is(setMiniLocations)) { | ||
// this is called on eval, with the mini locations obtained from the transpiler | ||
// codemirror will automatically remap the marks when the document is edited | ||
// create a mark for each mini location, adding the range to the spec to find it later | ||
const marks = e.value | ||
.filter(([from]) => from < tr.newDoc.length) | ||
.map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) | ||
.map( | ||
(range) => | ||
Decoration.mark({ | ||
id: range.join(':'), | ||
// this green is only to verify that the decoration moves when the document is edited | ||
// it will be removed later, so the mark is not visible by default | ||
attributes: { style: `background-color: #00CA2880` }, | ||
}).range(...range), // -> Decoration | ||
); | ||
|
||
locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet<Decoration> | ||
} | ||
} | ||
|
||
return locations; | ||
}, | ||
}); | ||
|
||
const visibleMiniLocations = StateField.define({ | ||
create() { | ||
return { atTime: 0, haps: new Map() }; | ||
}, | ||
update(visible, tr) { | ||
for (let e of tr.effects) { | ||
if (e.is(showMiniLocations)) { | ||
// this is called every frame to show the locations that are currently active | ||
// we can NOT create new marks because the context.locations haven't changed since eval time | ||
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range | ||
const haps = new Map(); | ||
for (let hap of e.value.haps) { | ||
for (let { start, end } of hap.context.locations) { | ||
let id = `${start}:${end}`; | ||
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { | ||
haps.set(id, hap); | ||
} | ||
} | ||
} | ||
|
||
visible = { atTime: e.value.atTime, haps }; | ||
} | ||
} | ||
|
||
return visible; | ||
}, | ||
}); | ||
|
||
// // Derive the set of decorations from the miniLocations and visibleLocations | ||
const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => { | ||
const iterator = state.field(miniLocations).iter(); | ||
const { haps } = state.field(visibleMiniLocations); | ||
const builder = new RangeSetBuilder(); | ||
|
||
while (iterator.value) { | ||
const { | ||
from, | ||
to, | ||
value: { | ||
spec: { id }, | ||
}, | ||
} = iterator; | ||
|
||
if (haps.has(id)) { | ||
const hap = haps.get(id); | ||
const color = hap.context.color ?? 'var(--foreground)'; | ||
// Get explicit channels for color values | ||
/* | ||
const swatch = document.createElement('div'); | ||
swatch.style.color = color; | ||
document.body.appendChild(swatch); | ||
let channels = getComputedStyle(swatch) | ||
.color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) | ||
.slice(1) | ||
.map((c) => parseFloat(c || 1)); | ||
document.body.removeChild(swatch); | ||
// Get percentage of event | ||
const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; | ||
channels[3] *= percent; | ||
*/ | ||
|
||
builder.add( | ||
from, | ||
to, | ||
Decoration.mark({ | ||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, | ||
attributes: { style: `outline: solid 2px ${color}` }, | ||
}), | ||
); | ||
} | ||
|
||
iterator.next(); | ||
} | ||
|
||
return builder.finish(); | ||
}); | ||
|
||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './codemirror.mjs'; | ||
export * from './highlight.mjs'; | ||
export * from './flash.mjs'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.