Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.
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
11 changes: 11 additions & 0 deletions api-editor/gui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api-editor/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@emotion/styled": "^11.9.3",
"@reduxjs/toolkit": "^1.8.3",
"chart.js": "^3.8.0",
"fastest-levenshtein": "^1.0.12",
"framer-motion": "^6.3.16",
"idb-keyval": "^6.2.0",
"katex": "^0.16.0",
Expand Down
44 changes: 42 additions & 2 deletions api-editor/gui/src/features/filter/FilterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
PopoverContent,
PopoverHeader,
PopoverTrigger,
Text as ChakraText,
UnorderedList,
} from '@chakra-ui/react';
import { closest, distance } from 'fastest-levenshtein';
import React from 'react';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import { selectFilterString, setFilterString } from '../ui/uiSlice';
import { isValidFilterToken } from './model/filterFactory';
import { getFixedFilterNames, isValidFilterToken } from './model/filterFactory';

export const FilterInput: React.FC = function () {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -51,7 +53,7 @@ export const FilterInput: React.FC = function () {
<PopoverBody>
<UnorderedList spacing={2}>
{invalidTokens.map((token) => (
<ListItem key={token}>{token}</ListItem>
<InvalidFilterToken key={token} token={token} />
))}
</UnorderedList>
</PopoverBody>
Expand All @@ -61,3 +63,41 @@ export const FilterInput: React.FC = function () {
</Box>
);
};

interface InvalidFilterTokenProps {
token: string;
}

const InvalidFilterToken: React.FC<InvalidFilterTokenProps> = function ({ token }) {
const dispatch = useAppDispatch();

const alternatives = getFixedFilterNames();
const closestAlternative = closest(token.toLowerCase(), alternatives);
const closestDistance = distance(token.toLowerCase(), closestAlternative);

const filterString = useAppSelector(selectFilterString);

const onClick = () => {
dispatch(setFilterString(filterString.replace(token, closestAlternative)));
};

return (
<ListItem>
<ChakraText>
<ChakraText display="inline" fontWeight="bold">
{token}
</ChakraText>
{closestDistance <= 3 && (
<>
{'. '}
Did you mean{' '}
<ChakraText display="inline" textDecoration="underline" cursor="pointer" onClick={onClick}>
{closestAlternative}
</ChakraText>
?
</>
)}
</ChakraText>
</ListItem>
);
};
122 changes: 53 additions & 69 deletions api-editor/gui/src/features/filter/model/filterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,81 +52,58 @@ const parsePotentiallyNegatedToken = function (token: string): Optional<Abstract
}
};

const fixedFilters: { [name: string]: AbstractPythonFilter } = {
// Declaration type
'is:module': new DeclarationTypeFilter(DeclarationType.Module),
'is:class': new DeclarationTypeFilter(DeclarationType.Class),
'is:function': new DeclarationTypeFilter(DeclarationType.Function),
'is:parameter': new DeclarationTypeFilter(DeclarationType.Parameter),

// Visibility
'is:public': new VisibilityFilter(Visibility.Public),
'is:internal': new VisibilityFilter(Visibility.Internal),

// Parameter required or optional
'is:required': new RequiredOrOptionalFilter(RequiredOrOptional.Required),
'is:optional': new RequiredOrOptionalFilter(RequiredOrOptional.Optional),

// Parameter assignment
'is:implicit': new ParameterAssignmentFilter(PythonParameterAssignment.IMPLICIT),
'is:positiononly': new ParameterAssignmentFilter(PythonParameterAssignment.POSITION_ONLY),
'is:positionorname': new ParameterAssignmentFilter(PythonParameterAssignment.POSITION_OR_NAME),
'is:positionalvararg': new ParameterAssignmentFilter(PythonParameterAssignment.POSITIONAL_VARARG),
'is:nameonly': new ParameterAssignmentFilter(PythonParameterAssignment.NAME_ONLY),
'is:namedvararg': new ParameterAssignmentFilter(PythonParameterAssignment.NAMED_VARARG),

// Done
'is:done': new DoneFilter(),

// Annotations
'annotation:any': new AnnotationFilter(AnnotationType.Any),
'annotation:@boundary': new AnnotationFilter(AnnotationType.Boundary),
'annotation:@calledafter': new AnnotationFilter(AnnotationType.CalledAfter),
'is:complete': new AnnotationFilter(AnnotationType.Complete), // Deliberate special case. It should be transparent to users it's an annotation.
'annotation:@description': new AnnotationFilter(AnnotationType.Description),
'annotation:@enum': new AnnotationFilter(AnnotationType.Enum),
'annotation:@group': new AnnotationFilter(AnnotationType.Group),
'annotation:@move': new AnnotationFilter(AnnotationType.Move),
'annotation:@pure': new AnnotationFilter(AnnotationType.Pure),
'annotation:@remove': new AnnotationFilter(AnnotationType.Remove),
'annotation:@rename': new AnnotationFilter(AnnotationType.Rename),
'annotation:@todo': new AnnotationFilter(AnnotationType.Todo),
'annotation:@value': new AnnotationFilter(AnnotationType.Value),
};

/**
* Handles a singe non-negated token.
*
* @param token The text that describes the filter.
*/
const parsePositiveToken = function (token: string): Optional<AbstractPythonFilter> {
// Filters with fixed text
switch (token.toLowerCase()) {
// Declaration type
case 'is:module':
return new DeclarationTypeFilter(DeclarationType.Module);
case 'is:class':
return new DeclarationTypeFilter(DeclarationType.Class);
case 'is:function':
return new DeclarationTypeFilter(DeclarationType.Function);
case 'is:parameter':
return new DeclarationTypeFilter(DeclarationType.Parameter);

// Visibility
case 'is:public':
return new VisibilityFilter(Visibility.Public);
case 'is:internal':
return new VisibilityFilter(Visibility.Internal);

// Parameter required or optional
case 'is:required':
return new RequiredOrOptionalFilter(RequiredOrOptional.Required);
case 'is:optional':
return new RequiredOrOptionalFilter(RequiredOrOptional.Optional);

// Parameter assignment
case 'is:implicit':
return new ParameterAssignmentFilter(PythonParameterAssignment.IMPLICIT);
case 'is:positiononly':
return new ParameterAssignmentFilter(PythonParameterAssignment.POSITION_ONLY);
case 'is:positionorname':
return new ParameterAssignmentFilter(PythonParameterAssignment.POSITION_OR_NAME);
case 'is:positionalvararg':
return new ParameterAssignmentFilter(PythonParameterAssignment.POSITIONAL_VARARG);
case 'is:nameonly':
return new ParameterAssignmentFilter(PythonParameterAssignment.NAME_ONLY);
case 'is:namedvararg':
return new ParameterAssignmentFilter(PythonParameterAssignment.NAMED_VARARG);

// Done
case 'is:done':
return new DoneFilter();

// Annotations
case 'annotation:any':
return new AnnotationFilter(AnnotationType.Any);
case 'annotation:@boundary':
return new AnnotationFilter(AnnotationType.Boundary);
case 'annotation:@calledafter':
return new AnnotationFilter(AnnotationType.CalledAfter);
case 'is:complete': // Deliberate special case. It should be transparent to users it's an annotation.
return new AnnotationFilter(AnnotationType.Complete);
case 'annotation:@description':
return new AnnotationFilter(AnnotationType.Description);
case 'annotation:@enum':
return new AnnotationFilter(AnnotationType.Enum);
case 'annotation:@group':
return new AnnotationFilter(AnnotationType.Group);
case 'annotation:@move':
return new AnnotationFilter(AnnotationType.Move);
case 'annotation:@pure':
return new AnnotationFilter(AnnotationType.Pure);
case 'annotation:@remove':
return new AnnotationFilter(AnnotationType.Remove);
case 'annotation:@rename':
return new AnnotationFilter(AnnotationType.Rename);
case 'annotation:@todo':
return new AnnotationFilter(AnnotationType.Todo);
case 'annotation:@value':
return new AnnotationFilter(AnnotationType.Value);
// Fixed filters
const fixedFilter = fixedFilters[token.toLowerCase()];
if (fixedFilter) {
return fixedFilter;
}

// Name
Expand Down Expand Up @@ -219,3 +196,10 @@ const comparisonFunction = function (comparisonOperator: string): ((a: number, b
export const isValidFilterToken = function (token: string): boolean {
return Boolean(parsePotentiallyNegatedToken(token));
};

/**
* Returns the names of all fixed filter like "annotation:any".
*/
export const getFixedFilterNames = function (): string[] {
return Object.keys(fixedFilters);
};