Skip to content

Commit

Permalink
Merge branch 'release/0.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Yeboster committed Mar 27, 2021
2 parents 0427125 + ab6c1e7 commit ea52f6c
Show file tree
Hide file tree
Showing 21 changed files with 2,085 additions and 98 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 0.7.0
- Improve sort order
- Add current file scanning 🎉
- Implement tokenizer for Arabic, Japanese languages and a default. Thanks @tadashi-aikawa!
- Close autocomplete on space
- Add statusbar for tokenizer
- Prioritize FlowProvider suggestions.

# 0.6.2
- Fix removeView

Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This plugin provides a text autocomplete feature to enhance typing speed.

<!-- TODO: Update preview gif -->

![Preview](https://media.giphy.com/media/CFbhjfTLDPnUm45vje/giphy.gif)

> The plugin is still WIP, so if you encounter bugs please open an [Github issue](https://github.com/Yeboster/autocomplete-obsidian/issues/new/choose) with the steps to reproduce it.
Expand All @@ -10,18 +12,22 @@ This plugin provides a text autocomplete feature to enhance typing speed.

There are the current and planned features:

- Trigger autocomplete with `ctrl+space` or `Toggle Autocomplete` command
- Change suggestion with `Ctrl-n/p` or `up/down arrows` and select with `enter`
- Autocomplete view style as Obsidian
- Seamless integration with vim mode
- Cursor placement on marks:
- Single cursor placement
- [ ] Multiple cursor placement on marks (Latex functions)
- Default autocomplete features:
- Trigger autocomplete with `ctrl+space` or `Toggle Autocomplete` command
- Change suggestion with `Ctrl-n/p` or `up/down arrows` and select with `enter`
- Autocomplete view style as Obsidian
- Seamless integration with vim mode
- Tokenizer for multiple languages (For now Arabic, Japanese and a default):
- Change default tokenizer in settings or click on statusbar (`strategy: ...`)
- Suggest completions with text providers:
- LaTex
- Flow (suggests words already written in the current session)
- [ ] Current file
- Current file (triggered on `change-file` and `load` events)
- Trigger manual scan of different language with command `Autocomplete: Scan current file (language)`
- [ ] Custom file
- Cursor placement for LaTeX functions:
- Single function param
- [ ] Multiple function params
- [ ] Snippets support (h3 -> ###)
- [ ] Proper layout management (Improve autocomplete popup position)
- [ ] Context aware (Latex trigger only inside `$$` block)
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-autocomplete-plugin",
"name": "Autocomplete",
"version": "0.6.2",
"version": "0.7.0",
"minAppVersion": "0.10.0",
"description": "This plugin provides a text autocomplete feature to enhance typing speed.",
"author": "Yeboster",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "autocomplete-obsidian",
"version": "0.6.2",
"version": "0.7.0",
"description": "An Obsidian plugin to provide text autocomplete.",
"main": "index.js",
"author": "Yeboster <yeboster@gmail.com>",
Expand Down
66 changes: 53 additions & 13 deletions src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import {
defaultDirection,
managePlaceholders,
updateSelectedSuggestionFrom,
lastWordIn,
copyObject,
lastWordFrom,
lastWordStartPos,
} from './autocomplete/core'
import {
generateView,
appendWidget,
updateCachedView,
scrollTo,
} from './autocomplete/view'

import { FlowProvider } from './providers/flow'
import { TokenizeStrategy } from './providers/flow/tokenizer'
import { TokenizerFactory } from './providers/flow/factory'
import LaTexProvider from './providers/latex'
import { Completion, Provider } from './providers/provider'

import { AutocompleteSettings } from './settings/settings'

import { TFile } from 'obsidian'

export class Autocomplete {
private providers: Provider[]
private suggestions: Completion[]
Expand All @@ -39,37 +42,45 @@ export class Autocomplete {
this.view = null
}

public isShown(): boolean {
public get isShown(): boolean {
return this.view !== null
}

public toggleViewIn(editor: CodeMirror.Editor) {
const isEnabled = this.settings.enabled
if (this.isShown() || !isEnabled) {
if (this.isShown || !isEnabled) {
this.cursorAtTrigger = null
this.removeViewFrom(editor)
} else if (isEnabled) {
const cursor = copyObject(editor.getCursor())
const currentLine: string = editor.getLine(cursor.line)

const wordStartIndex = lastWordStartPos(currentLine, cursor.ch)
const wordStartIndex = this.tokenizer.lastWordStartPos(
currentLine,
cursor.ch
)
const cursorAt = cursor.ch
cursor.ch = wordStartIndex
this.cursorAtTrigger = cursor

const word = lastWordFrom(currentLine, cursor.ch)
const word = currentLine.slice(wordStartIndex, cursorAt)

this.showViewIn(editor, word)
}
}

public updateViewIn(editor: CodeMirror.Editor, event: KeyboardEvent) {
if (!event.ctrlKey && event.key === ' ') return this.removeViewFrom(editor)

this.selected = updateSelectedSuggestionFrom(
event,
this.selected,
this.suggestions.length
)

const completionWord = lastWordIn(editor)
const cursor = copyObject(editor.getCursor())
const currentLine: string = editor.getLine(cursor.line)
const completionWord = this.tokenizer.lastWordFrom(currentLine, cursor.ch)

const recreate = completionWord !== this.lastCompletionWord
if (recreate) {
Expand Down Expand Up @@ -98,21 +109,50 @@ export class Autocomplete {
}

public updateProvidersFrom(event: KeyboardEvent, editor: CodeMirror.Editor) {
if (!event.ctrlKey && Provider.wordSeparatorRegex.test(event.key)) {
const tokenizer = TokenizerFactory.getTokenizer(
this.settings.flowProviderTokenizeStrategy
)
if (
!event.ctrlKey &&
(tokenizer.isWordSeparator(event.key) || event.key === 'Enter')
) {
const cursor = copyObject(editor.getCursor())
if (/Enter/.test(event.key)) {
cursor.line -= 1
cursor.ch = editor.getLine(cursor.line).length
const currentLine = editor.getLine(cursor.line)

// Changed editor pane
if (!currentLine) return

cursor.ch = currentLine.length
}
const line = editor.getLine(cursor.line)
this.providers.forEach((provider) => {
// For now only FlowProvider
if (provider instanceof FlowProvider)
provider.addCompletionWord(line, cursor.ch)
provider.addLastWordFrom(line, cursor.ch, this.tokenizerStrategy)
})
}
}

public scanFile(file: TFile, strategy: TokenizeStrategy = 'default') {
const providers = this.providers
file.vault.read(file).then((content: string) => {
// TODO: Make it async
providers.forEach((provider) => {
if (provider instanceof FlowProvider) provider.addWordsFrom(content)
})
})
}

private get tokenizer() {
return TokenizerFactory.getTokenizer(this.tokenizerStrategy)
}

private get tokenizerStrategy() {
return this.settings.flowProviderTokenizeStrategy
}

private showViewIn(editor: CodeMirror.Editor, completionWord: string = '') {
if (this.view) this.removeViewFrom(editor)

Expand Down Expand Up @@ -168,7 +208,7 @@ export class Autocomplete {
if (hintId && hintId.startsWith(hintIdPrefix)) {
hintId = hintId.replace(hintIdPrefix, '')
const id = parseInt(hintId)
if (id && id > 0 && id < this.suggestions.length) {
if (id >= 0 && id < this.suggestions.length) {
this.selected.index = id
this.selectSuggestion(editor)
}
Expand Down Expand Up @@ -209,8 +249,8 @@ export class Autocomplete {

private loadProviders() {
const providers = []
if (this.settings.latexProvider) providers.push(new LaTexProvider())
if (this.settings.flowProvider) providers.push(new FlowProvider())
if (this.settings.latexProvider) providers.push(new LaTexProvider())

this.providers = providers
}
Expand Down
27 changes: 0 additions & 27 deletions src/autocomplete/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,6 @@ export function defaultDirection(): Direction {
return { index: 0, direction: 'still' }
}

export function lastWordStartPos(text: string, index: number): number {
let wordStartIndex = index
const wordRegex = /[\w$]+/
while (wordStartIndex && wordRegex.test(text.charAt(wordStartIndex - 1)))
wordStartIndex -= 1

return wordStartIndex
}

export function lastWordIn(editor: CodeMirror.Editor): string | null {
const cursor = editor.getCursor()
const currentLine: string = editor.getLine(cursor.line)

const word = lastWordFrom(currentLine, cursor.ch)

return word
}

export function lastWordFrom(line: string, cursorIndex: number): string | null {
let wordStartIndex = lastWordStartPos(line, cursorIndex)
let word: string | null = null
if (wordStartIndex !== cursorIndex)
word = line.slice(wordStartIndex, cursorIndex)

return word
}

export function managePlaceholders(
selectedValue: string,
initialCursorIndex: number
Expand Down
62 changes: 60 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { MarkdownView, Plugin } from 'obsidian'
import { MarkdownView, Notice, Plugin, TFile } from 'obsidian'
import { Autocomplete } from './autocomplete'
import { TOKENIZE_STRATEGIES } from './providers/flow/tokenizer'
import { AutocompleteSettings } from './settings/settings'
import { AutocompleteSettingsTab } from './settings/settings-tab'
import { StatusBarView } from './statusbar'

export default class AutocompletePlugin extends Plugin {
private autocomplete: Autocomplete
private lastUsedEditor: CodeMirror.Editor

private statusBar: StatusBarView

settings: AutocompleteSettings

async onload() {
Expand All @@ -18,6 +23,7 @@ export default class AutocompletePlugin extends Plugin {

if (!this.settings.enabled) return

this.statusBar = new StatusBarView(this, this.settings)
this.enable()
this.addCommands()
}
Expand Down Expand Up @@ -54,34 +60,86 @@ export default class AutocompletePlugin extends Plugin {
}
},
})

this.addScanCommands()
}

enable() {
this.autocomplete = new Autocomplete(this.settings)

const settings = this.settings
if (settings.flowProviderScanCurrent)
// Passing autocomplete as context
this.app.workspace.on('file-open', this.onFileOpened, this)

this.registerCodeMirror((editor) => {
editor.on('keyup', this.keyUpListener)
})

if (settings.flowProviderScanCurrent) {
this.statusBar.addStatusBar()
const file = this.app.workspace.getActiveFile()
this.autocomplete.scanFile(file, settings.flowProviderTokenizeStrategy)
}
}

disable() {
const workspace = this.app.workspace
// Always remove to avoid any kind problem
workspace.off('file-open', this.onFileOpened)

this.statusBar.removeStatusBar()

workspace.iterateCodeMirrors((cm) => {
cm.off('keyup', this.keyUpListener)
this.autocomplete.removeViewFrom(cm)
})
}

private addScanCommands() {
TOKENIZE_STRATEGIES.forEach((type) => {
const capitalized = type.replace(/^\w/, (c) => c.toLocaleUpperCase())
const name = `Scan current file ${
type !== 'default' ? `(${capitalized})` : ''
}`

this.addCommand({
id: `autocomplete-scan-current-file-${type}`,
name,
callback: () => {
if (!this.settings.flowProviderScanCurrent) {
new Notice(
'Please activate setting flow Provider: Scan current file'
)
}

const autocomplete = this.autocomplete
const editor = this.getValidEditorFor(autocomplete)

if (editor) {
const file = this.app.workspace.getActiveFile()
autocomplete.scanFile(file, type)
}
},
})
})
}

private keyUpListener = (editor: CodeMirror.Editor, event: KeyboardEvent) => {
const autocomplete = this.autocomplete
autocomplete.updateProvidersFrom(event, editor)

if (!autocomplete.isShown()) return
if (!autocomplete.isShown) return

this.updateEditorIfChanged(editor, autocomplete)

this.autocomplete.updateViewIn(editor, event)
}

private onFileOpened(file: TFile) {
this.autocomplete.scanFile(file)
}

private getValidEditorFor(
autocomplete: Autocomplete
): CodeMirror.Editor | null {
Expand Down
Loading

0 comments on commit ea52f6c

Please sign in to comment.