Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Find", "Find and replace", and "Jump to line" keyboard shortcuts #343

Merged
merged 37 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ee28b06
Add find and replace functionality
michaeltaranto Feb 26, 2024
a568b6a
Shift code below search bar
michaeltaranto Feb 26, 2024
a75448d
Prevent browser shortcuts while search is focused
michaeltaranto Feb 26, 2024
07a4caa
Add tests
michaeltaranto Feb 27, 2024
74b4f5a
Assert focus before running commands
michaeltaranto Feb 27, 2024
7f5d7c3
Merge branch 'master' into add-search-replace
michaeltaranto Feb 29, 2024
65ec147
Change 'Replace (⌘R)' to 'Find and replace (⌘⌥F)'
felixhabib Mar 1, 2024
2b79671
Refactor tests
michaeltaranto Mar 1, 2024
b7b0c39
Add waits
michaeltaranto Mar 1, 2024
90719ad
Type escape differently
michaeltaranto Mar 1, 2024
019a6df
Whitespace 😘
michaeltaranto Mar 1, 2024
aae4268
Increase waits
michaeltaranto Mar 1, 2024
893e393
Replace get search field with get dialog and find search field
felixhabib Mar 1, 2024
465b28e
Try forcing and retrying
felixhabib Mar 1, 2024
09d82a5
Try without force
felixhabib Mar 1, 2024
ef2e11e
Add back force and refactor
felixhabib Mar 1, 2024
ccd143b
Always force for `typeInSearchField`
felixhabib Mar 1, 2024
f33cd0f
Fix typo
felixhabib Mar 1, 2024
b87ff0a
Fix changeset
felixhabib Mar 1, 2024
4517ced
Try without waits
felixhabib Mar 3, 2024
abc2e79
Add wait for `jumpToLine` only
felixhabib Mar 3, 2024
b49ca5c
Add back all waits
felixhabib Mar 3, 2024
db25e34
Merge branch 'master' into add-search-replace
felixhabib Mar 5, 2024
e8aceb9
Remove retries
felixhabib Mar 5, 2024
86a597b
Rollback typecode change
felixhabib Mar 5, 2024
84c49ab
Remove `assertCodePaneHasFocus`
felixhabib Mar 5, 2024
f6ef488
Add comment to explain waits
felixhabib Mar 5, 2024
33df989
Try without force
felixhabib Mar 5, 2024
54100c1
Revert "Try without force"
felixhabib Mar 5, 2024
3343d81
Fix incorrectly written test
felixhabib Mar 6, 2024
49b73a0
Dumbify back out of replace test
felixhabib Mar 6, 2024
3104b96
Merge branch 'master' into add-search-replace
felixhabib Mar 6, 2024
c704dff
Dumbify jumpToLine function
felixhabib Mar 6, 2024
b1ea7c7
Format
felixhabib Mar 6, 2024
40a1243
Update changeset
felixhabib Mar 7, 2024
dd21a42
Update changeset for brevity and clarity
felixhabib Mar 7, 2024
e44a3d2
Ditto
felixhabib Mar 7, 2024
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
11 changes: 11 additions & 0 deletions .changeset/good-starfishes-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'playroom': minor
---

Add "Find", "Find and replace", and "Jump to line" functionality.

Keybindings for these new commands are:

- `Cmd + F` / `Ctrl + F` - Find
- `Cmd + Option + F` / `Ctrl + Alt + F` - Find and replace
- `Cmd + G` / `Ctrl + G` - Jump to line
169 changes: 163 additions & 6 deletions cypress/e2e/keymaps.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
typeCode,
assertCodePaneContains,
loadPlayroom,
cmdPlus,
selectNextWords,
selectNextLines,
selectNextCharacters,
Expand All @@ -11,14 +12,13 @@ import {
moveToEndOfLine,
moveBy,
moveByWords,
assertCodePaneSearchMatchesCount,
findInCode,
replaceInCode,
jumpToLine,
} from '../support/utils';
import { isMac } from '../../src/utils/formatting';

const cmdPlus = (keyCombo) => {
const platformSpecificKey = isMac() ? 'cmd' : 'ctrl';
return `${platformSpecificKey}+${keyCombo}`;
};

describe('Keymaps', () => {
describe('swapLine', () => {
beforeEach(() => {
Expand Down Expand Up @@ -350,6 +350,163 @@ describe('Keymaps', () => {
});
});

describe('find and replace', () => {
beforeEach(() => {
loadPlayroom(`
<div>First line</div>
<div>Second line</div>
<div>Third line</div>
`);
});

it('should find all occurrences of search term', () => {
findInCode('div');

assertCodePaneSearchMatchesCount(6);

cy.focused().type('{esc}');

typeCode('c');

assertCodePaneContains(dedent`
<c>First line</div>
<div>Second line</div>
<div>Third line</div>
`);
});

it('should replace and skip occurrences of search term correctly', () => {
replaceInCode('div', 'span');

// replace occurrence
cy.get('.CodeMirror-dialog button').contains('Yes').click();

assertCodePaneContains(dedent`
<span>First line</div>
<div>Second line</div>
<div>Third line</div>
`);

// ignore occurrence
cy.get('.CodeMirror-dialog button').contains('No').click();

assertCodePaneContains(dedent`
<span>First line</div>
<div>Second line</div>
<div>Third line</div>
`);

// replace occurrence
cy.get('.CodeMirror-dialog button').contains('Yes').click();

assertCodePaneContains(dedent`
<span>First line</div>
<span>Second line</div>
<div>Third line</div>
`);

// replace all remaining occurrences
cy.get('.CodeMirror-dialog button').contains('All').click();

assertCodePaneContains(dedent`
<span>First line</span>
<span>Second line</span>
<span>Third line</span>
`);

typeCode('c');

assertCodePaneContains(dedent`
<span>First line</span>
<span>Second line</spanc>
<span>Third line</span>
`);
});

it('should back out of replace correctly', () => {
replaceInCode('div');

typeCode('{esc}');

assertCodePaneContains(dedent`
<div>First line</div>
<div>Second line</div>
<div>Third line</div>
`);

typeCode('c');

assertCodePaneContains(dedent`
c<div>First line</div>
<div>Second line</div>
<div>Third line</div>
`);
});
});

describe('jump to line', () => {
beforeEach(() => {
loadPlayroom(`
<div>First line</div>
<div>Second line</div>
<div>Third line</div>
<div>Forth line</div>
<div>Fifth line</div>
<div>Sixth line</div>
<div>Seventh line</div>
`);
});

it('should jump to line number correctly', () => {
const line = 6;
jumpToLine(line);

typeCode('c');

assertCodePaneContains(dedent`
<div>First line</div>
<div>Second line</div>
<div>Third line</div>
<div>Forth line</div>
<div>Fifth line</div>
c<div>Sixth line</div>
<div>Seventh line</div>
`);

typeCode('{backspace}');

const nextLine = 2;
jumpToLine(nextLine);

typeCode('c');

assertCodePaneContains(dedent`
<div>First line</div>
c<div>Second line</div>
<div>Third line</div>
<div>Forth line</div>
<div>Fifth line</div>
<div>Sixth line</div>
<div>Seventh line</div>
`);
});

it('should jump to line and column number correctly', () => {
jumpToLine('6:10');
typeCode('a');

assertCodePaneContains(dedent`
<div>First line</div>
<div>Second line</div>
<div>Third line</div>
<div>Forth line</div>
<div>Fifth line</div>
<div>Sixtha line</div>
<div>Seventh line</div>
`);
});
});

describe('toggleComment', () => {
const blockStarter = `
<div>First line</div>
Expand Down Expand Up @@ -1461,7 +1618,7 @@ describe('Keymaps', () => {
});
});

describe('for an selection beginning during opening comment syntax', () => {
describe('for a selection beginning during opening comment syntax', () => {
it('block', () => {
loadPlayroom(`
<div>
Expand Down
56 changes: 56 additions & 0 deletions cypress/support/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import dedent from 'dedent';
import { createUrl } from '../../utils';
import { isMac } from '../../src/utils/formatting';

export const cmdPlus = (keyCombo) => {
const platformSpecificKey = isMac() ? 'cmd' : 'ctrl';
return `${platformSpecificKey}+${keyCombo}`;
};

const getCodeEditor = () =>
cy.get('.CodeMirror-code').then((editor) => cy.wrap(editor));

Expand Down Expand Up @@ -182,3 +187,54 @@ export const loadPlayroom = (initialCode) => {
indexedDB.deleteDatabase(storageKey);
});
};

const typeInSearchField = (text) =>
/*
force true is required because cypress incorrectly and intermittently
reports that search field is covered by another element
*/
cy.get('.CodeMirror-search-field').type(text, { force: true });

/**
* @param {string} term
*/
export const findInCode = (term) => {
// Wait necessary to ensure code pane is focussed
cy.wait(200); // eslint-disable-line @finsit/cypress/no-unnecessary-waiting
typeCode(`{${cmdPlus('f')}}`);

typeInSearchField(`${term}{enter}`);
};

/**
* @param {string} term
* @param {string} [replaceWith]
*/
export const replaceInCode = (term, replaceWith) => {
// Wait necessary to ensure code pane is focussed
cy.wait(200); // eslint-disable-line @finsit/cypress/no-unnecessary-waiting
typeCode(`{${cmdPlus('alt+f')}}`);
typeInSearchField(`${term}{enter}`);
if (replaceWith) {
typeInSearchField(`${replaceWith}{enter}`);
}
};

/**
* @param {number} line
*/
export const jumpToLine = (line) => {
// Wait necessary to ensure code pane is focussed
cy.wait(200); // eslint-disable-line @finsit/cypress/no-unnecessary-waiting
typeCode(`{${cmdPlus('g')}}`);
typeCode(`${line}{enter}`);
};

/**
* @param {number} lines
*/
export const assertCodePaneSearchMatchesCount = (lines) => {
getCodeEditor().within(() =>
cy.get('.cm-searching').should('have.length', lines)
);
};
100 changes: 99 additions & 1 deletion src/Playroom/CodeEditor/CodeEditor.css.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { style, globalStyle, keyframes } from '@vanilla-extract/css';
import { style, globalStyle, keyframes, createVar } from '@vanilla-extract/css';
import { vars, colorPaletteVars, sprinkles } from '../sprinkles.css';
import { toolbarItemSize } from '../ToolbarItem/ToolbarItem.css';

const minimumLineNumberWidth = '50px';

Expand Down Expand Up @@ -224,3 +225,100 @@ globalStyle('.cm-s-neo .cm-variable', {
globalStyle('.cm-s-neo .cm-number', {
color: colorPaletteVars.code.number,
});

globalStyle('.CodeMirror-dialog', {
paddingLeft: vars.space.xlarge,
paddingRight: vars.space.xlarge,
minHeight: toolbarItemSize,
borderBottom: `1px solid ${colorPaletteVars.border.standard}`,
display: 'flex',
alignItems: 'center',
});

const searchOffset = createVar();
globalStyle('.CodeMirror-scroll', {
transform: `translateY(${searchOffset})`,
transition: vars.transition.fast,
});

globalStyle('.dialog-opened .CodeMirror-scroll', {
vars: {
[searchOffset]: `${toolbarItemSize}px`,
},
});

globalStyle('.dialog-opened .CodeMirror-lines', {
paddingBottom: searchOffset,
});

globalStyle('.CodeMirror-dialog input', {
font: vars.font.scale.large,
fontFamily: vars.font.family.code,
height: vars.touchableSize,
flexGrow: 1,
});

globalStyle('.CodeMirror-search-hint', {
display: 'none',
});

globalStyle('.CodeMirror-search-label', {
display: 'flex',
alignItems: 'center',
minHeight: vars.touchableSize,
font: vars.font.scale.large,
fontFamily: vars.font.family.code,
});

globalStyle('.CodeMirror-search-field', {
paddingLeft: vars.space.xlarge,
});

globalStyle('label.CodeMirror-search-label', {
flexGrow: 1,
});

globalStyle('.dialog-opened.cm-s-neo .CodeMirror-selected', {
background: colorPaletteVars.background.search,
});

globalStyle('.cm-overlay.cm-searching', {
paddingTop: 2,
paddingBottom: 2,
background: colorPaletteVars.background.selection,
});

globalStyle('.CodeMirror-dialog button:first-of-type', {
marginLeft: vars.space.xlarge,
});

globalStyle('.CodeMirror-dialog button', {
appearance: 'none',
font: vars.font.scale.standard,
fontFamily: vars.font.family.standard,
marginLeft: vars.space.medium,
paddingTop: vars.space.medium,
paddingBottom: vars.space.medium,
paddingLeft: vars.space.large,
paddingRight: vars.space.large,
alignSelf: 'center',
display: 'block',
background: 'none',
borderRadius: vars.radii.large,
cursor: 'pointer',
border: '1px solid currentColor',
});

globalStyle('.CodeMirror-dialog button:focus', {
color: colorPaletteVars.foreground.accent,
boxShadow: colorPaletteVars.shadows.focus,
outline: 'none',
});

globalStyle('.CodeMirror-dialog button:focus:hover', {
background: colorPaletteVars.background.selection,
});

globalStyle('.CodeMirror-dialog button:hover', {
background: colorPaletteVars.background.transparent,
});
Loading