-
Notifications
You must be signed in to change notification settings - Fork 80
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
Autocompletion and Syntax Highlight. #2171
Closed
kareltucek
wants to merge
23
commits into
UltimateHackingKeyboard:master
from
kareltucek:autocompletion
Closed
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
491ddec
Integrate autocompletion and a lexer specific to uhk.
kareltucek 69fad64
Remove unused testing code.
kareltucek df53a1f
Try to satisfy linter?
kareltucek 9f7e985
Try to appease CI runner?
kareltucek fab9a25
Try appease web build?
kareltucek a807b2d
Appease web build.
kareltucek b210c3a
Override some colors.
kareltucek 39a4d9d
Remove unwanted packages.
kareltucek 42853dd
Adjust colors.
kareltucek 26049de
Update naive parser.
kareltucek fd17c3d
Fix package lock.
kareltucek 77ee9c6
Update naive-autocompletion-parser. (Huge performance improvement.)
kareltucek 75a90a6
Update naive autocompletion parser.
kareltucek 0e9da81
Upgrade the parser.
kareltucek c598815
Show nicer icons, and fix some grammar problems.
kareltucek e215106
Order completion items intelligently.
kareltucek 4c1f260
linter fixes.
kareltucek 4ba8c45
Upgrade naive parser.
kareltucek ac7a77d
Upgrade naive parser - fix problems in prefix unification.
kareltucek 01c98e7
try ci again?
kareltucek 062515c
Merge remote-tracking branch 'origin/master' into autocompletion
kareltucek 518c9db
Address review findings.
kareltucek ff78f64
Fix linter.
kareltucek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
packages/uhk-web/src/app/components/macro/action-editor/tab/command/completion-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { retrieveUhkGrammar, buildUhkParser, ParserBuilder, Parser, Suggestion } from 'naive-autocompletion-parser'; | ||
import { LogService } from 'uhk-common'; | ||
|
||
export class CustomCompletionProvider implements monaco.languages.CompletionItemProvider { | ||
public readonly triggerCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.\\@( \\$".split(""); | ||
|
||
private parser: Parser | undefined; | ||
private identifierCharPattern = /[a-zA-Z]/; | ||
|
||
constructor( private logService: LogService ) { | ||
this.parser = undefined; | ||
|
||
// TODO: here, pass the actual reference-manual.md (from correct repository/tag) body into the buildUhkParser | ||
retrieveUhkGrammar().then ( grammarText => { | ||
this.parser = buildUhkParser(grammarText); | ||
}); | ||
} | ||
|
||
guessKind(text: string, rule: string): [monaco.languages.CompletionItemKind, string] { | ||
if (rule.slice(rule.length-7) == '_ABBREV') { | ||
// key and scancode abbreviations | ||
return [monaco.languages.CompletionItemKind.Struct, "5"]; | ||
} else if (rule == 'MODMASK') { | ||
// modmask | ||
return [monaco.languages.CompletionItemKind.Interface, "4"]; | ||
} else if (this.identifierCharPattern.test(text[0])) { | ||
// identifiers, commands | ||
return [monaco.languages.CompletionItemKind.Text, "3"]; | ||
} else if (text[0] == '<' && text[text.length-1] == '>') { | ||
// hints | ||
return [monaco.languages.CompletionItemKind.Property, "1"]; | ||
} else if (text[1] == '<' && text[text.length-2] == '>') { | ||
// comment hints | ||
return [monaco.languages.CompletionItemKind.Property, "1"]; | ||
} else { | ||
// operators | ||
return [monaco.languages.CompletionItemKind.Module, "2"]; | ||
} | ||
} | ||
|
||
provideCompletionItems( | ||
model: monaco.editor.ITextModel, | ||
position: monaco.Position, | ||
context: monaco.languages.CompletionContext, | ||
token: monaco.CancellationToken | ||
): monaco.languages.ProviderResult<monaco.languages.CompletionList> { | ||
const lineNumber = position.lineNumber; | ||
const column = position.column; | ||
|
||
const lineText = model.getValueInRange({ | ||
startLineNumber: lineNumber, | ||
startColumn: 1, | ||
endLineNumber: lineNumber, | ||
endColumn: column | ||
}); | ||
|
||
if (this.parser) { | ||
let nelaSuggestions: Suggestion[] = []; | ||
try { | ||
nelaSuggestions = this.parser.complete(lineText, "BODY"); | ||
} catch (e) { | ||
this.logService.error(e); | ||
} | ||
let monacoSuggestions: monaco.languages.CompletionItem[] = nelaSuggestions.map(it => { | ||
let kind = this.guessKind(it.suggestion, it.originRule); | ||
return { | ||
insertText: it.text(), | ||
kind: kind[0], | ||
label: it.text(), | ||
sortText: kind[1] + it.text(), | ||
range: { | ||
startLineNumber: lineNumber, | ||
startColumn: column - it.overlap, | ||
endLineNumber: lineNumber, | ||
endColumn: column | ||
}, | ||
}; | ||
}); | ||
return { | ||
suggestions: monacoSuggestions | ||
}; | ||
} else { | ||
return { | ||
suggestions: [] | ||
}; | ||
} | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
packages/uhk-web/src/app/components/macro/action-editor/tab/command/highlight-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
export function highlightProvider(): monaco.languages.IMonarchLanguage { | ||
return { | ||
keywords: [], | ||
|
||
typeKeywords: [], | ||
|
||
operators: [ | ||
'=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=', | ||
'&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%', | ||
'<<', '>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=', '^=', | ||
'%=', '<<=', '>>=', '>>>=' | ||
], | ||
|
||
// we include these common regular expressions | ||
symbols: /[=><!~?:&|+\-*/^%]+/, | ||
|
||
// C# style strings | ||
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, | ||
|
||
// The main tokenizer for our languages | ||
tokenizer: { | ||
root: [ | ||
// identifiers and keywords | ||
[/[a-z_][\w$]*/, { | ||
cases: { | ||
'@typeKeywords': 'keyword', | ||
'@keywords': 'keyword', | ||
'@default': 'identifier' | ||
} | ||
}], | ||
[/\$[A-Za-z_][\w$]*/, 'type.identifier'], // to show class names nicely | ||
|
||
// numbers | ||
[/\d*\.\d+([eE][-+]?\d+)?/, 'number.float'], | ||
[/\d+/, 'number'], | ||
[/true|false/, 'number'], | ||
|
||
// whitespace | ||
{ include: '@whitespace' }, | ||
|
||
// delimiters and operators | ||
[/[{}()[\]]/, '@brackets'], | ||
[/[<>](?!@symbols)/, '@brackets'], | ||
[/@symbols/, { | ||
cases: { | ||
'@operators': 'operator', | ||
'@default': '' | ||
} | ||
}], | ||
|
||
// delimiter: after number because of .\d floats | ||
[/[;,.]/, 'delimiter'], | ||
|
||
// strings | ||
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string | ||
[/"/, { token: 'string.quote', bracket: '@open', next: '@string' }], | ||
|
||
// literalStrings | ||
[/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string | ||
[/'/, { token: 'string.quote', bracket: '@open', next: '@literalString' }], | ||
], | ||
|
||
string: [ | ||
[/[^\\"]+/, 'string'], | ||
[/@escapes/, 'string.escape'], | ||
[/\\./, 'string.escape.invalid'], | ||
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }] | ||
], | ||
|
||
literalString: [ | ||
[/[^\\']+/, 'string'], | ||
[/\\./, 'string.escape.invalid'], | ||
[/'/, { token: 'string.quote', bracket: '@close', next: '@pop' }] | ||
], | ||
|
||
whitespace: [ | ||
[/[ \t\r\n]+/, 'white'], | ||
[/\/\/.*$/, 'comment'], | ||
], | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,13 +25,15 @@ import { LogService } from 'uhk-common'; | |
import { SelectedMacroActionId } from '../../../../../models'; | ||
import { SmartMacroDocCommandAction, SmartMacroDocService } from '../../../../../services/smart-macro-doc-service'; | ||
import { hasNonAsciiCharacters, NON_ASCII_REGEXP } from '../../../../../util'; | ||
import { CustomCompletionProvider } from './completion-provider'; | ||
import { highlightProvider } from './highlight-provider'; | ||
|
||
const MONACO_EDITOR_LINE_HEIGHT_OPTION = 66; | ||
const MONACO_EDITOR_LF_END_OF_LINE_OPTION = 0; | ||
const MACRO_CHANGE_DEBOUNCE_TIME = 250; | ||
|
||
function getVsCodeTheme(): string { | ||
return (window as any).getUhkTheme() === 'dark' ? 'uhk-dark' : 'vs'; | ||
return (window as any).getUhkTheme() === 'dark' ? 'uhk-dark' : 'uhk-light'; | ||
} | ||
|
||
@Component({ | ||
|
@@ -85,6 +87,7 @@ export class MacroCommandEditorComponent implements AfterViewInit, ControlValueA | |
private insertingMacro = false; | ||
private changeObserver$: Observer<string>; | ||
private subscriptions = new Subscription(); | ||
private static completionRegistered: boolean = false; | ||
|
||
constructor(private cdRef: ChangeDetectorRef, | ||
@Inject(DOCUMENT) private document: Document, | ||
|
@@ -141,6 +144,7 @@ export class MacroCommandEditorComponent implements AfterViewInit, ControlValueA | |
if (this.editor && this.autoFocus) { | ||
this.editor.focus(); | ||
} | ||
|
||
this.calculateHeight(); | ||
} | ||
|
||
|
@@ -163,10 +167,42 @@ export class MacroCommandEditorComponent implements AfterViewInit, ControlValueA | |
this.removeNonAsciiCharachters(); | ||
} | ||
|
||
private setLanguageProperties(editor: MonacoStandaloneCodeEditor) { | ||
const languageId = 'uhkScript'; | ||
|
||
if (!MacroCommandEditorComponent.completionRegistered) { | ||
MacroCommandEditorComponent.completionRegistered = true; | ||
monaco.languages.register({ id: languageId }); | ||
|
||
// Register the custom completion provider | ||
const completionProvider = new CustomCompletionProvider(this.logService); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use Angular built-in dependency injection. https://angular.io/guide/architecture-services |
||
const providerRegistration = monaco.languages.registerCompletionItemProvider( | ||
languageId, | ||
completionProvider | ||
); | ||
|
||
// Register highlighter | ||
monaco.languages.setMonarchTokensProvider( | ||
languageId, | ||
highlightProvider() | ||
); | ||
} | ||
|
||
editor.getModel()?.dispose(); | ||
const newModel = monaco.editor.createModel( | ||
editor.getValue(), | ||
languageId | ||
); | ||
editor.setModel(newModel); | ||
} | ||
|
||
onEditorInit(editor: MonacoStandaloneCodeEditor) { | ||
this.logService.misc('[MacroCommandEditorComponent] editor initialized.'); | ||
|
||
this.editor = editor; | ||
|
||
this.setLanguageProperties(editor); | ||
|
||
this.setLFEndOfLineOption(); | ||
if (this.autoFocus) { | ||
this.editor.focus(); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This static variable is not really good for manage new language service registration, because if someone upgrade the firmware we have to apply the new language service.
We have to
registerCompletionItemProvider
andsetMonarchTokensProvider
.We could register these new providers as we register the new themes in the
MonacoEditorCustomThemeService
. The difference is we have to re-register the providers every time when the configurations changed.We can create a new
MonacoEditorUhkLanguageService
this service has to subscribe themonacoLoaderService.isMonacoLoaded$
and when the newgetLanguageServiceConfig
. To combine 2 observers we could use combineLatest function.How to create the
getLanguageServiceConfig
observerable?We can extend the
packages/uhk-web/src/app/store/reducers/smart-macro-doc.reducer.ts
or create a new reducer. to be continue tomorrowThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Agent has 2 main "process" the electron thread and the browser thread. The 2 world communicate with each other via electron built in inter-process communication
The
packages/uhk-common/src/util/ipcEvents.ts
file contains all of the ipc events that Agent uses. I tried to organise them. When the main module reply to an event in async mode I used theReply
suffix.Every time when the user opens the smart macro document panel.
SmartMacroDoc.downloadDocumentation
event.This moment is too late to load the auto-complete md file, because maybe the user start using the smart macro editor before opens the smart macro doc panel.
I think everytime when Agents starts or updates the firmware then have to check the smart macro document is available or not. If it not available, then have to download it and after provide the auto complete markdown file to the renderer process.
If we use the web version of the Agent then when Agents start we have to download the auto complete markdown file from the Agent repo.
To achieve this functionality we have to refactor many small things. I don't know should I able to write every step in high level. My assumption is to provide a really step-by-step guide I have to implement the whole code in my mind. I am afraid if I forget something maybe I create a bigger problem for you than guidance.
I think faster if I finish the PR. Unfortunately, my availability will very limited in the next 3 weeks.
I am happy to help you to learn Agent coding, but I think we have to start with a smaller issue if you don't mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allright, it is yours then.
No worries ;)