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 options for search input #38

Merged
merged 9 commits into from
Mar 21, 2022
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: 3 additions & 3 deletions jupyterlab_search_replace/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def initialize(self) -> None:
async def get(self, path: str = ""):
query = self.get_query_argument("query")
max_count = self.get_query_argument("max_count", 100)
case_sensitive = self.get_query_argument("case_sensitive", False)
whole_word = self.get_query_argument("whole_word", False)
case_sensitive = self.get_query_argument("case_sensitive", "false") == "true"
whole_word = self.get_query_argument("whole_word", "false") == "true"
include = self.get_query_argument("include", None)
exclude = self.get_query_argument("exclude", None)
use_regex = self.get_query_argument("use_regex", False)
use_regex = self.get_query_argument("use_regex", "false") == "true"
try:
r = await self._engine.search(
query,
Expand Down
6 changes: 3 additions & 3 deletions jupyterlab_search_replace/tests/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def test_search_no_match(test_content, schema, jp_fetch):

async def test_search_case_sensitive(test_content, schema, jp_fetch):
response = await jp_fetch(
"search", params={"query": "Strange", "case_sensitive": True}, method="GET"
"search", params={"query": "Strange", "case_sensitive": "true"}, method="GET"
)
assert response.code == 200
payload = json.loads(response.body)
Expand All @@ -107,7 +107,7 @@ async def test_search_case_sensitive(test_content, schema, jp_fetch):

async def test_search_whole_word(test_content, schema, jp_fetch):
response = await jp_fetch(
"search", params={"query": "strange", "whole_word": True}, method="GET"
"search", params={"query": "strange", "whole_word": "true"}, method="GET"
)
assert response.code == 200
payload = json.loads(response.body)
Expand Down Expand Up @@ -273,7 +273,7 @@ async def test_search_literal(test_content, schema, jp_fetch):

async def test_search_regex(test_content, schema, jp_fetch):
response = await jp_fetch(
"search", params={"query": "str.*", "use_regex": True}, method="GET"
"search", params={"query": "str.*", "use_regex": "true"}, method="GET"
)
assert response.code == 200
payload = json.loads(response.body)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@jupyterlab/builder": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"@vscode/codicons": "^0.0.29",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^3.1.4",
Expand Down
8 changes: 8 additions & 0 deletions src/icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LabIcon } from '@jupyterlab/ui-components';

import wholeWord from '../style/icons/whole-word.svg';

export const wholeWordIcon = new LabIcon({
name: 'search-replace:wholeWord',
svgstr: wholeWord
});
141 changes: 127 additions & 14 deletions src/searchReplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,33 @@ import { Debouncer } from '@lumino/polling';
import { CommandRegistry } from '@lumino/commands';
import { requestAPI } from './handler';
import { VDomModel, VDomRenderer } from '@jupyterlab/apputils';
import { wholeWordIcon } from './icon';
import {
Search,
TreeView,
TreeItem,
Badge,
Progress
Progress,
Button
} from '@jupyter-notebook/react-components';
import { caseSensitiveIcon, regexIcon } from '@jupyterlab/ui-components';

export class SearchReplaceModel extends VDomModel {
constructor() {
super();
this._isLoading = false;
this._searchString = '';
this._queryResults = [];
this._caseSensitive = false;
this._wholeWord = false;
this._useRegex = false;
this._debouncedStartSearch = new Debouncer(() => {
this.getSearchString(this._searchString);
this.getSearchString(
this._searchString,
this._caseSensitive,
this._wholeWord,
this._useRegex
);
});
}

Expand Down Expand Up @@ -49,15 +60,74 @@ export class SearchReplaceModel extends VDomModel {
}
}

get caseSensitive(): boolean {
return this._caseSensitive;
}

set caseSensitive(v: boolean) {
if (v !== this._caseSensitive) {
this._caseSensitive = v;
this.stateChanged.emit();
this._debouncedStartSearch
.invoke()
.catch(reason =>
console.error(`failed query for ${v} due to ${reason}`)
);
}
}

get wholeWord(): boolean {
return this._wholeWord;
}

set wholeWord(v: boolean) {
if (v !== this._wholeWord) {
this._wholeWord = v;
this.stateChanged.emit();
this._debouncedStartSearch
.invoke()
.catch(reason =>
console.error(`failed query for ${v} due to ${reason}`)
);
}
}

get useRegex(): boolean {
return this._useRegex;
}

set useRegex(v: boolean) {
if (v !== this._useRegex) {
this._useRegex = v;
this.stateChanged.emit();
this._debouncedStartSearch
.invoke()
.catch(reason =>
console.error(`failed query for ${v} due to ${reason}`)
);
}
}

get queryResults(): IResults[] {
return this._queryResults;
}

async getSearchString(search: string): Promise<void> {
private async getSearchString(
search: string,
caseSensitive: boolean,
wholeWord: boolean,
useRegex: boolean
): Promise<void> {
try {
this.isLoading = true;
const data = await requestAPI<IQueryResult>(
'?' + new URLSearchParams([['query', search]]).toString(),
'?' +
new URLSearchParams([
['query', search],
['case_sensitive', caseSensitive.toString()],
['whole_word', wholeWord.toString()],
['use_regex', useRegex.toString()]
]).toString(),
{
method: 'GET'
}
Expand All @@ -75,6 +145,9 @@ export class SearchReplaceModel extends VDomModel {

private _isLoading: boolean;
private _searchString: string;
private _caseSensitive: boolean;
private _wholeWord: boolean;
private _useRegex: boolean;
private _queryResults: IResults[];
private _debouncedStartSearch: Debouncer;
}
Expand Down Expand Up @@ -168,22 +241,62 @@ export class SearchReplaceView extends VDomRenderer<SearchReplaceModel> {
commands={this._commands}
isLoading={this.model.isLoading}
queryResults={this.model.queryResults}
/>
>
<Button
title="button to enable case sensitive mode"
appearance={this.model.caseSensitive === true ? 'accent' : 'neutral'}
onClick={() => {
this.model.caseSensitive = !this.model.caseSensitive;
}}
>
<caseSensitiveIcon.react></caseSensitiveIcon.react>
</Button>
<Button
title="button to enable whole word mode"
appearance={this.model.wholeWord === true ? 'accent' : 'neutral'}
onClick={() => {
this.model.wholeWord = !this.model.wholeWord;
}}
>
<wholeWordIcon.react></wholeWordIcon.react>
</Button>
<Button
title="button to enable use regex mode"
appearance={this.model.useRegex === true ? 'accent' : 'neutral'}
onClick={() => {
this.model.useRegex = !this.model.useRegex;
}}
>
<regexIcon.react></regexIcon.react>
</Button>
</SearchReplaceElement>
);
}
}

const SearchReplaceElement = (props: any) => {
interface IProps {
searchString: string;
queryResults: IResults[];
commands: CommandRegistry;
isLoading: boolean;
onSearchChanged: (s: string) => void;
children: React.ReactNode;
}

const SearchReplaceElement = (props: IProps) => {
return (
<>
<Search
appearance="outline"
placeholder="Search"
aria-label="Search files for text"
onInput={(event: any) => {
props.onSearchChanged(event.target.value);
}}
/>
<div className="search-bar-with-options">
<Search
appearance="outline"
placeholder="Search"
aria-label="Search files for text"
onInput={(event: any) => {
props.onSearchChanged(event.target.value);
}}
/>
{props.children}
</div>
{props.isLoading ? (
<Progress />
) : (
Expand Down
4 changes: 4 additions & 0 deletions src/svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.svg' {
const value: string;
export default value;
}
12 changes: 12 additions & 0 deletions style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@
.search-tree-files > span {
flex-grow: 1;
}

.search-bar-with-options {
display: flex;
}

.search-bar-with-options > * {
flex: 0 0 auto;
}

.search-bar-with-options > jp-search:first-child {
flex: 1 1 auto;
}
5 changes: 5 additions & 0 deletions style/icons/whole-word.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions ui-tests/tests/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,78 @@ test('should get no matches', async ({ page }) => {
await page.waitForSelector('#jp-search-replace >> text="No Matches Found"')
).toBeTruthy();
});

test('should test for case sensitive option', async ({ page }) => {
madhur-tandon marked this conversation as resolved.
Show resolved Hide resolved
// Click #tab-key-0 .lm-TabBar-tabIcon svg >> nth=0
await page.locator('[title="Search and replace"]').click();
// Fill input[type="search"]
await page.locator('input[type="search"]').fill('Strange');

let response_url: string;

await Promise.all([
page.waitForResponse(
response => {
response_url = response.url();
return /.*search\/\?query=Strange/.test(response.url()) &&
response.request().method() === 'GET'
}
),
page.locator('input[type="search"]').press('Enter'),
page.locator('[title="button to enable case sensitive mode"]').click()
]);

expect(/case_sensitive=true/.test(response_url)).toEqual(true);

expect(await page.waitForSelector('jp-tree-view[role="tree"] >> text=1')).toBeTruthy();
});

test('should test for whole word option', async ({ page }) => {
// Click #tab-key-0 .lm-TabBar-tabIcon svg >> nth=0
await page.locator('[title="Search and replace"]').click();
// Fill input[type="search"]
await page.locator('input[type="search"]').fill('strange');

let response_url: string;

await Promise.all([
page.waitForResponse(
response => {
response_url = response.url();
return /.*search\/\?query=strange/.test(response.url()) &&
response.request().method() === 'GET'
}
),
page.locator('input[type="search"]').press('Enter'),
page.locator('[title="button to enable whole word mode"]').click()
]);

expect(/whole_word=true/.test(response_url)).toEqual(true);

expect(await page.waitForSelector('jp-tree-view[role="tree"] >> text=4')).toBeTruthy();
});

test('should test for use regex option', async ({ page }) => {
// Click #tab-key-0 .lm-TabBar-tabIcon svg >> nth=0
await page.locator('[title="Search and replace"]').click();
// Fill input[type="search"]
await page.locator('input[type="search"]').fill('str.*');

let response_url: string;

await Promise.all([
page.waitForResponse(
response => {
response_url = response.url();
return /.*search\/\?query=str.\*/.test(response.url()) &&
response.request().method() === 'GET'
}
),
page.locator('input[type="search"]').press('Enter'),
page.locator('[title="button to enable use regex mode"]').click()
]);

expect(/use_regex=true/.test(response_url)).toEqual(true);

expect(await page.waitForSelector('jp-tree-view[role="tree"] >> text=5')).toBeTruthy();
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,11 @@
resolved "https://registry.yarnpkg.com/@verdaccio/ui-theme/-/ui-theme-6.0.0-6-next.16.tgz#f88f555b502636c37ec1722d832c6fd826b63892"
integrity sha512-FbYl3273qaA0/fRwrvE876/HuvU81zjsnR70rCEojBelDuddl3xbY1LVdvthCjUGuIj2SUNpTzGhyROdqHJUCg==

"@vscode/codicons@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@vscode/codicons/-/codicons-0.0.29.tgz#587e6ce4be8de55d9d11e5297ea55a3dbab08ccf"
integrity sha512-AXhTv1nl3r4W5DqAfXXKiawQNW+tLBNlXn/GcsnFCL0j17sQ2AY+az9oB9K6wjkibq1fndNJvmT8RYN712Fdww==

"@webassemblyjs/[email protected]":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
Expand Down