Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CoreEditor/src/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,8 @@ function observeEventsForCompletion() {
}, true);
}

const storage: { isMouseDown: boolean } = { isMouseDown: false };
const storage: {
isMouseDown: boolean;
} = {
isMouseDown: false,
};
2 changes: 2 additions & 0 deletions CoreEditor/src/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { languages } from './@vendor/language-data';

import { loadTheme } from './styling/themes';
import { markdownExtensions, renderExtensions, actionExtensions } from './styling/markdown';
import { lineIndicatorLayer } from './styling/nodes/line';
import { gutterExtensions } from './styling/nodes/gutter';

import { localizePhrases } from './modules/localization';
Expand Down Expand Up @@ -108,6 +109,7 @@ export function extensions(options: { lineBreak?: string }) {
selectedLines.of([]),
renderExtensions,
actionExtensions,
lineIndicatorLayer, // Must after highlightActiveLine

// Input handling
wordTokenizer(),
Expand Down
6 changes: 5 additions & 1 deletion CoreEditor/src/modules/history/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ export function isContentDirty() {
return storage.savedUndoDepth !== undoDepth(window.editor.state);
}

const storage: { savedUndoDepth: number } = { savedUndoDepth: 0 };
const storage: {
savedUndoDepth: number;
} = {
savedUndoDepth: 0,
};
11 changes: 6 additions & 5 deletions CoreEditor/src/styling/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,14 @@ function buildTheme(colors: EditorColors, scheme?: ColorScheme) {
backgroundColor: `${colors.selection} !important`, // #180 !important is needed by macOS 14 sdk
},
'.cm-activeLine': {
backgroundColor: colors.activeLine,
boxShadow: innerBorder(2.5, colors.lineBorder),
// Intentionally hide the effects here, use lineIndicatorLayer to render instead
backgroundColor: 'transparent !important',
boxShadow: 'none !important',
},
// Brackets
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
backgroundColor: colors.matchingBracket,
boxShadow: innerBorder(1.5, colors.bracketBorder),
boxShadow: buildInnerBorder(1.5, colors.bracketBorder),
},
// Gutters
'.cm-gutters': {
Expand Down Expand Up @@ -229,8 +230,8 @@ function buildHighlight(colors: EditorColors, specs: readonly TagStyle[], scheme
/**
* Please use box-shadow to create inner borders.
*/
function innerBorder(width: number, color?: string) {
function buildInnerBorder(width: number, color?: string) {
return color !== undefined ? `inset 0px 0px 0px ${width}px ${color}` : 'none';
}

export { buildTheme, buildHighlight, tags };
export { buildTheme, buildHighlight, buildInnerBorder, tags };
100 changes: 100 additions & 0 deletions CoreEditor/src/styling/nodes/line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { RectangleMarker, layer } from '@codemirror/view';
import { buildInnerBorder } from '../builder';

const borderWidth = 2.5;

/**
* Line level indentations make the background of active lines partially drawn,
* use this home-made version to have full-width indicators.
*
* It just finds all active lines and creates wider layers to "replace" them.
*/
export const lineIndicatorLayer = layer({
class: 'cm-md-activeLine',
above: false,
markers: editor => {
// Fail fast if line indicator is disabled
if (!window.config.showActiveLineIndicator) {
return [];
}

const content = editor.contentDOM;
const lines = [...content.querySelectorAll('.cm-activeLine')] as HTMLElement[];
return lines.map(line => new Layer(content, line, storage.cachedTheme));
},
update: update => {
// Theme changed, the update object doesn't have sufficient info
if (window.config.theme !== storage.cachedTheme) {
storage.cachedTheme = window.config.theme;
return true;
}

return update.selectionSet || update.docChanged || update.viewportChanged || update.geometryChanged;
},
});

class Layer extends RectangleMarker {
// Used for object equality
private readonly rect: DOMRect;

constructor(content: HTMLElement, line: HTMLElement, private readonly theme?: string) {
const lineRect = line.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();

// We use box shadow to draw inner borders, use an inset to hide the left and right borders
const borderInset = (() => {
if (window.colors?.lineBorder === undefined) {
return 0;
}

// Slightly bigger to avoid precision issues
return borderWidth + 1;
})();

// The rect is wider than lineRect, it fills the entire contentDOM
const rectToDraw = new DOMRect(
contentRect.left - borderInset, // x
line.offsetTop, // y
contentRect.width + borderInset * 2, // width
lineRect.height, // height
);

super('cm-md-activeIndicator', rectToDraw.left, rectToDraw.top, rectToDraw.width, rectToDraw.height);
this.rect = rectToDraw;
}

draw() {
const elt = super.draw();
this.render(elt);
return elt;
}

update(elt: HTMLElement, prev: Layer): boolean {
this.render(elt);
return super.update(elt, prev);
}

eq(other: Layer): boolean {
const almostEq = (a: number, b: number): boolean => {
return Math.abs(a - b) < 0.001;
};

return this.theme === other.theme &&
almostEq(this.rect.x, other.rect.x) &&
almostEq(this.rect.y, other.rect.y) &&
almostEq(this.rect.width, other.rect.width) &&
almostEq(this.rect.height, other.rect.height);
}

private render(elt: HTMLElement) {
const colors = window.colors;
elt.style.backgroundColor = colors?.activeLine ?? '';
elt.style.boxShadow = buildInnerBorder(borderWidth, colors?.lineBorder);
}
}

const storage: {
cachedTheme?: string;
} = {
cachedTheme: undefined,
};
84 changes: 5 additions & 79 deletions CoreEditor/src/styling/nodes/list.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Decoration, layer, RectangleMarker } from '@codemirror/view';
import { Decoration } from '@codemirror/view';
import { createDecoPlugin } from '../helper';
import { createDecos } from '../matchers/lexer';

const canvas = document.createElement('canvas');
const className = 'cm-md-indentedList';

/**
* Decorate list items with indentation info.
* List item indentation, text is always aligned to bullets for soft breaks.
*/
const lineDecoPlugin = createDecoPlugin(() => {
export const indentedListStyle = createDecoPlugin(() => {
return createDecos('ListItem', listItem => {
if (shouldDisablePlugins()) {
// Fail fast if line wrapping is disabled
if (!window.config.lineWrapping) {
return null;
}

Expand Down Expand Up @@ -58,79 +59,6 @@ const lineDecoPlugin = createDecoPlugin(() => {
});
});

/**
* Indentations make the active line background partially drawn,
* draw extra background with a layer to fill the entire line.
*/
const activeLineFiller = layer({
class: 'cm-md-listActiveLine',
above: false,
markers: () => {
if (shouldDisablePlugins()) {
return [];
}

// Fill all active lines that are decorated as indented list
const lists = [...document.querySelectorAll(`.cm-activeLine.${className}`)] as HTMLElement[];
return lists.map(list => new BackgroundMarker(list, storage.cachedTheme));
},
update: update => {
if (window.config.theme !== storage.cachedTheme) {
storage.cachedTheme = window.config.theme;
return true;
}

return update.selectionSet || update.docChanged || update.viewportChanged || update.geometryChanged;
},
});

/**
* List item indentation, text is always aligned to bullets for soft breaks.
*/
export const indentedListStyle = [
lineDecoPlugin,
activeLineFiller,
];

/**
* Marker that extends active line background color to fill its parent.
*/
class BackgroundMarker extends RectangleMarker {
private readonly color: string;

constructor(anchor: HTMLElement, private readonly theme?: string) {
const rect = anchor.getBoundingClientRect();
super('cm-md-listActiveBackground', 0, anchor.offsetTop, anchor.offsetLeft, rect.bottom - rect.top);

const style = getComputedStyle(anchor);
this.color = style.backgroundColor;
}

draw() {
const elt = super.draw();
this.render(elt);
return elt;
}

update(elt: HTMLElement, prev: BackgroundMarker): boolean {
this.render(elt);
return super.update(elt, prev);
}

eq(other: BackgroundMarker): boolean {
return this.theme === other.theme && super.eq(other);
}

private render(elt: HTMLElement) {
elt.style.backgroundColor = this.color;
}
}

function shouldDisablePlugins() {
// Indented style is meaningful only when line wrapping is enabled
return !window.config.lineWrapping;
}

function getTextIndent(text: string) {
const font = `${window.config.fontSize}px ${window.config.fontFamily}`;
const key = text + font;
Expand All @@ -149,8 +77,6 @@ function getTextIndent(text: string) {

const storage: {
cachedIndents: { [key: string]: number };
cachedTheme?: string;
} = {
cachedIndents: {},
cachedTheme: undefined,
};