Skip to content

Commit

Permalink
fix(editor): highlight line calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
exuanbo committed Dec 6, 2023
1 parent 83db6a6 commit 32a566e
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 27 deletions.
77 changes: 75 additions & 2 deletions __tests__/common/maybe.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fromNullable, just, nothing } from '@/common/maybe'
import { fromFalsy, fromNullable, just, nothing } from '@/common/maybe'

describe('Maybe', () => {
describe('fromNullable', () => {
Expand All @@ -8,11 +8,55 @@ describe('Maybe', () => {
expect(result.extractNullable()).toBe(1)
})

it('should create a Maybe with a nullable value', () => {
it('should create a Maybe with null', () => {
const result = fromNullable<number>(null)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})

it('should create a Maybe with undefined', () => {
const result = fromNullable<number>(undefined)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})
})

describe('fromFalsy', () => {
it('should create a Maybe with a value', () => {
const result = fromFalsy(1)
expect(result.extract()).toBe(1)
expect(result.extractNullable()).toBe(1)
})

it('should create a Maybe with null', () => {
const result = fromFalsy<number>(null)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})

it('should create a Maybe with undefined', () => {
const result = fromFalsy<number>(undefined)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})

it('should create a Maybe with false', () => {
const result = fromFalsy<number>(false)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})

it('should create a Maybe with 0', () => {
const result = fromFalsy<number>(0)
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})

it('should create a Maybe with an empty string', () => {
const result = fromFalsy<string>('')
expect(result.extract()).toBe(undefined)
expect(result.extractNullable()).toBe(null)
})
})

describe('just', () => {
Expand Down Expand Up @@ -67,6 +111,35 @@ describe('Maybe', () => {
})
})

describe('orDefaultLazy', () => {
it('should return a value', () => {
const result = fromNullable(1).orDefaultLazy(() => 2)
expect(result).toBe(1)
})

it('should return a default value', () => {
const result = fromNullable<number>(null).orDefaultLazy(() => 2)
expect(result).toBe(2)
})
})

describe('filter', () => {
it('should filter with truthy predicate', () => {
const result = fromNullable(1).filter((x) => x === 1)
expect(result.extract()).toBe(1)
})

it('should filter with falsy predicate', () => {
const result = fromNullable(1).filter((x) => x === 2)
expect(result.extract()).toBe(undefined)
})

it('should filter a nullable value', () => {
const result = fromNullable<number>(null).filter((x) => x === 1)
expect(result.extract()).toBe(undefined)
})
})

describe('ifJust', () => {
it('should call a function with a value', () => {
expect.assertions(1)
Expand Down
12 changes: 11 additions & 1 deletion src/common/maybe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface Maybe<T extends {}> {
map: <U extends {}>(f: (value: T) => U) => Maybe<U>
chain: <U extends {}>(f: (value: T) => Maybe<U>) => Maybe<U>
orDefault: <U>(defaultValue: U) => T | U
orDefaultLazy: <U>(getDefaultValue: () => U) => T | U
filter: (pred: (value: T) => boolean) => Maybe<T>
extract: () => T | undefined
extractNullable: () => T | null
ifJust: (f: (value: T) => void) => this
Expand All @@ -19,6 +21,8 @@ export const just = <T extends {}>(value: T): Maybe<T> => {
map: (f) => just(f(value)),
chain: (f) => f(value),
orDefault: () => value,
orDefaultLazy: () => value,
filter: (pred) => (pred(value) ? instance : nothing()),
extract: () => value,
extractNullable: () => value,
ifJust: (f) => (f(value), instance),
Expand All @@ -34,6 +38,8 @@ export const nothing = <T extends {}>(): Maybe<T> => {
map: () => nothing(),
chain: () => nothing(),
orDefault: (defaultValue) => defaultValue,
orDefaultLazy: (getDefaultValue) => getDefaultValue(),
filter: () => instance,
extract: () => undefined,
extractNullable: () => null,
ifJust: () => instance,
Expand All @@ -44,4 +50,8 @@ export const nothing = <T extends {}>(): Maybe<T> => {

type MaybeFromNullable = <T extends {}>(value: Nullable<T>) => Maybe<T>

export const fromNullable: MaybeFromNullable = (value) => (value ? just(value) : nothing())
export const fromNullable: MaybeFromNullable = (value) => (value != null ? just(value) : nothing())

type MaybeFromFalsy = <T extends {}>(value: Nullable<T> | false | 0 | 0n | '') => Maybe<T>

export const fromFalsy: MaybeFromFalsy = (value) => (value ? just(value) : nothing())
25 changes: 13 additions & 12 deletions src/features/editor/editorSlice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { EditorView } from '@codemirror/view'
import { createSelector, createSlice, type PayloadAction } from '@reduxjs/toolkit'

import { fromFalsy, fromNullable } from '@/common/maybe'
import type { SourceRange, Statement } from '@/features/assembler/core'

import { type LineLoc, lineRangesEqual } from './codemirror/text'
Expand Down Expand Up @@ -84,22 +85,22 @@ export const editorSlice = createSlice({
selectors: {
selectEditorInput: (state) => state.input,
selectEditorHighlightLinePos: createSelector(
[(state: EditorState) => state.highlightRange, (_: EditorState, view: EditorView) => view],
(highlightRange, view) => {
if (highlightRange === null) {
return undefined
}
const linePos: number[] = []
for (let pos = highlightRange.from; pos < highlightRange.to; pos++) {
if (pos < view.state.doc.length) {
const line = view.state.doc.lineAt(pos)
[
(state: EditorState) => state.highlightRange,
(_: EditorState, view: EditorView) => view.state.doc,
],
(highlightRange, doc) =>
fromNullable(highlightRange).chain((range) => {
const linePos: number[] = []
const rangeTo = Math.min(range.to, doc.length)
for (let pos = range.from; pos < rangeTo; pos++) {
const line = doc.lineAt(pos)
if (!linePos.includes(line.from)) {
linePos.push(line.from)
}
}
}
return linePos.length > 0 ? linePos : undefined
},
return fromFalsy(linePos.length && linePos)
}),
),
selectEditorBreakpoints: (state) => state.breakpoints,
selectEditorMessage: (state) => state.message,
Expand Down
27 changes: 15 additions & 12 deletions src/features/editor/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,26 +189,29 @@ export const useHighlightLine = (): void => {

useViewEffect((view) => {
const highlightLinePos$ = store.onState(curryRight2(selectEditorHighlightLinePos)(view))
return subscribe(highlightLinePos$, (linePos) => {
const shouldAddHighlight = linePos !== undefined
return subscribe(highlightLinePos$, (maybeLinePos) => {
view.dispatch({
effects: shouldAddHighlight
? linePos.map((pos, posIndex) =>
effects: maybeLinePos
.map((linePos) =>
linePos.map((pos, posIndex) =>
HighlightLineEffect.of({
pos,
// clear previous decorations on first line
filter: () => posIndex !== 0,
}),
)
: HighlightLineEffect.of({ filter: () => false }),
),
)
.orDefaultLazy(() => HighlightLineEffect.of({ filter: () => false })),
})
if (!view.hasFocus && shouldAddHighlight) {
view.dispatch({
// length of `linePos` is already checked
selection: { anchor: linePos[0] },
scrollIntoView: true,
maybeLinePos
.filter(() => !view.hasFocus)
.ifJust((linePos) => {
view.dispatch({
// length of `linePos` is already checked
selection: { anchor: linePos[0] },
scrollIntoView: true,
})
})
}
})
}, [])
}
Expand Down

0 comments on commit 32a566e

Please sign in to comment.