-
Notifications
You must be signed in to change notification settings - Fork 49
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
Rewrite of the abbreviation feature. #240
Merged
gebner
merged 7 commits into
leanprover:master
from
hediet:hediet/abbreviation-feature-rewrite
Jan 19, 2021
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2ab9037
Rewrite of the abbreviation feature.
hediet ce67fb0
Fixed selection positioning bug.
hediet 62af8a5
Fixes selection correction after expanding f<<>>.
hediet c0d9523
Fixes bug that happens when an abbreviation is ended with the abbrevi…
hediet 9b29d60
Fixes issues from PR.
hediet 53fa20a
Fixes formatting in extension.ts.
hediet 5f534ae
Merge remote-tracking branch 'origin/master' into hediet/abbreviation…
gebner 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
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
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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,48 @@ | ||
import { HoverProvider, TextDocument, Position, Hover, Range } from 'vscode'; | ||
import { AbbreviationProvider } from './AbbreviationProvider'; | ||
import { AbbreviationConfig } from './config'; | ||
|
||
/** | ||
* Adds hover behaviour for getting translations of unicode characters. | ||
* Eg: "Type ⊓ using \glb or \sqcap" | ||
*/ | ||
export class AbbreviationHoverProvider implements HoverProvider { | ||
constructor( | ||
private readonly config: AbbreviationConfig, | ||
private readonly abbrevations: AbbreviationProvider | ||
) {} | ||
|
||
provideHover(document: TextDocument, pos: Position): Hover | undefined { | ||
const context = document.lineAt(pos.line).text.substr(pos.character); | ||
const symbolsAtCursor = this.abbrevations.findSymbolsIn(context); | ||
const allAbbrevs = symbolsAtCursor.map((symbol) => ({ | ||
symbol, | ||
abbrevs: this.abbrevations.getAllAbbreviations(symbol), | ||
})); | ||
|
||
if ( | ||
allAbbrevs.length === 0 || | ||
allAbbrevs.every((a) => a.abbrevs.length === 0) | ||
) { | ||
return undefined; | ||
} | ||
|
||
const leader = this.config.abbreviationCharacter.get(); | ||
|
||
const hoverMarkdown = allAbbrevs | ||
.map( | ||
({ symbol, abbrevs }) => | ||
`Type ${symbol} using ${abbrevs | ||
.map((a) => '`' + leader + a + '`') | ||
.join(' or ')}` | ||
) | ||
.join('\n\n'); | ||
|
||
const maxSymbolLength = Math.max( | ||
...allAbbrevs.map((a) => a.symbol.length) | ||
); | ||
const hoverRange = new Range(pos, pos.translate(0, maxSymbolLength)); | ||
|
||
return new Hover(hoverMarkdown, hoverRange); | ||
} | ||
} |
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,95 @@ | ||
import { computed } from 'mobx'; | ||
import { Disposable } from 'vscode'; | ||
import { autorunDisposable } from '../utils/autorunDisposable'; | ||
import * as abbreviations from './abbreviations.json'; | ||
import { SymbolsByAbbreviation, AbbreviationConfig } from './config'; | ||
|
||
/** | ||
* Answers queries to a database of abbreviations. | ||
*/ | ||
export class AbbreviationProvider implements Disposable { | ||
private readonly disposables = new Array<Disposable>(); | ||
private cache: Record<string, string | undefined> = {}; | ||
|
||
constructor(private readonly config: AbbreviationConfig) { | ||
this.disposables.push( | ||
autorunDisposable(() => { | ||
// For the livetime of this component, cache the computed's | ||
const _ = this.symbolsByAbbreviation; | ||
// clear cache on change | ||
this.cache = {}; | ||
}) | ||
); | ||
} | ||
|
||
@computed | ||
private get symbolsByAbbreviation(): SymbolsByAbbreviation { | ||
// There are only like 1000 symbols. Building an index is not required yet. | ||
return { | ||
...abbreviations, | ||
...this.config.inputModeCustomTranslations.get(), | ||
}; | ||
} | ||
|
||
getAllAbbreviations(symbol: string): string[] { | ||
return Object.entries(this.symbolsByAbbreviation) | ||
.filter(([abbr, sym]) => sym === symbol) | ||
.map(([abbr]) => abbr); | ||
} | ||
|
||
findSymbolsIn(symbolPlusUnknown: string): string[] { | ||
const result = new Set<string>(); | ||
for (const [abbr, sym] of Object.entries(this.symbolsByAbbreviation)) { | ||
if (symbolPlusUnknown.startsWith(sym)) { | ||
result.add(sym); | ||
} | ||
} | ||
return [...result.values()]; | ||
} | ||
|
||
getReplacementText(abbrev: string): string | undefined { | ||
if (abbrev in this.cache) { | ||
return this.cache[abbrev]; | ||
} | ||
const result = this._getReplacementText(abbrev); | ||
this.cache[abbrev] = result; | ||
return result; | ||
} | ||
|
||
private _getReplacementText(abbrev: string): string | undefined { | ||
const matchingSymbol = this.findSymbolsByAbbreviationPrefix(abbrev)[0]; | ||
if (matchingSymbol) { | ||
return matchingSymbol; | ||
} else if (abbrev.length > 0) { | ||
const prefixReplacement = this.getReplacementText( | ||
abbrev.slice(0, abbrev.length - 1) | ||
); | ||
if (prefixReplacement) { | ||
return prefixReplacement + abbrev.slice(abbrev.length - 1); | ||
} | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
getSymbolForAbbreviation(abbrev: string): string | undefined { | ||
return this.symbolsByAbbreviation[abbrev]; | ||
} | ||
|
||
findSymbolsByAbbreviationPrefix(abbrevPrefix: string): string[] { | ||
const matchingAbbreviations = Object.keys( | ||
this.symbolsByAbbreviation | ||
).filter((abbrev) => abbrev.startsWith(abbrevPrefix)); | ||
|
||
matchingAbbreviations.sort((a, b) => a.length - b.length); | ||
return matchingAbbreviations.map( | ||
(abbr) => this.symbolsByAbbreviation[abbr] | ||
); | ||
} | ||
|
||
dispose(): void { | ||
for (const d of this.disposables) { | ||
d.dispose(); | ||
} | ||
} | ||
} |
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,33 @@ | ||
# Abbreviation Feature | ||
|
||
Edit [abbreviations.json](./abbreviations.json) to add common abbreviations. | ||
Use `$CURSOR` to set the new location of the cursor after replacement. | ||
|
||
## Caveat | ||
|
||
If VS Code adds certain characters automatically (like `]` after typing `[`), | ||
ensure that each such subword is a strict prefix of another abbreviation. | ||
|
||
### Example | ||
|
||
Assume that there are the abbreviations `[] -> A` and `[[]] -> B` and that the user wants to get the symbol `B`, so they type | ||
|
||
- `\`, full text: `\` | ||
- `[`, full text: `\[]` - this is a longest abbreviation! It gets replaced with `A`. | ||
- `[`, full text: `A[]` - this is not what the user wanted. | ||
|
||
Instead, also add the abbreviation `[]_ -> A`: | ||
|
||
- `\`, full text: `\` | ||
- `[`, full text: `\[]` - this could be either `\[]` or `\[]_`. | ||
- `[`, full text: `\[[]]` - this matches the longest abbreviation `[[]]`, so it gets replaced with `B`. | ||
|
||
# Demos | ||
|
||
## Eager Replacing | ||
|
||
![Eager Replacing Demo](../../media/abbreviation-eager-replacing.gif) | ||
|
||
## Multiple Cursors | ||
|
||
![Multi Cursor Demo](../../media/abbreviation-multi-cursor.gif) |
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
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,36 @@ | ||
import { serializerWithDefault, VsCodeSetting } from '../utils/VsCodeSetting'; | ||
|
||
/** | ||
* Exposes (observable) settings for the abbreviation feature. | ||
*/ | ||
export class AbbreviationConfig { | ||
readonly inputModeEnabled = new VsCodeSetting('lean.input.enabled', { | ||
serializer: serializerWithDefault(true), | ||
}); | ||
|
||
readonly abbreviationCharacter = new VsCodeSetting('lean.input.leader', { | ||
serializer: serializerWithDefault('\\'), | ||
}); | ||
|
||
readonly languages = new VsCodeSetting('lean.input.languages', { | ||
serializer: serializerWithDefault(['lean']), | ||
}); | ||
|
||
readonly inputModeCustomTranslations = new VsCodeSetting( | ||
'lean.input.customTranslations', | ||
{ | ||
serializer: serializerWithDefault<SymbolsByAbbreviation>({}), | ||
} | ||
); | ||
|
||
readonly eagerReplacementEnabled = new VsCodeSetting( | ||
'lean.input.eagerReplacementEnabled', | ||
{ | ||
serializer: serializerWithDefault(true), | ||
} | ||
); | ||
} | ||
|
||
export interface SymbolsByAbbreviation { | ||
[abbrev: string]: string; | ||
} |
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,33 @@ | ||
import { Disposable, languages } from 'vscode'; | ||
import { autorunDisposable } from '../utils/autorunDisposable'; | ||
import { AbbreviationHoverProvider } from './AbbreviationHoverProvider'; | ||
import { AbbreviationProvider } from './AbbreviationProvider'; | ||
import { AbbreviationRewriterFeature } from './rewriter/AbbreviationRewriterFeature'; | ||
import { AbbreviationConfig } from './config'; | ||
|
||
export class AbbreviationFeature { | ||
private readonly disposables = new Array<Disposable>(); | ||
|
||
constructor() { | ||
const config = new AbbreviationConfig(); | ||
const abbrevations = new AbbreviationProvider(config); | ||
|
||
this.disposables.push( | ||
autorunDisposable((disposables) => { | ||
disposables.push( | ||
languages.registerHoverProvider( | ||
config.languages.get(), | ||
new AbbreviationHoverProvider(config, abbrevations) | ||
) | ||
); | ||
}), | ||
new AbbreviationRewriterFeature(config, abbrevations) | ||
); | ||
} | ||
|
||
dispose(): void { | ||
for (const d of this.disposables) { | ||
d.dispose(); | ||
} | ||
} | ||
} |
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 setting is turned off to allow fields that can be mutated privately (
private _foo: string
) but that are readonly publically (get foo(): string { return this._foo; }
).