import { getDirFiles, exists, pathRelativeToHome } from '../../../support/utils' import { createVim, renameCurrentToCwd } from '../../../core/instance-manager' import { RowNormal, RowImportant } from '../../row-container' import { vimBlur, vimFocus } from '../../../ui/uikit' import { Plugin } from '../../plugin-container' import Input from '../../text-input' import { join, sep, basename } from 'path' import { filter } from 'fuzzaldrin-plus' import api from '../../../core/instance-api' import { homedir } from 'os' import { render } from 'inferno' const $HOME = homedir() interface FileDir { name: string file: boolean dir: boolean } let state = { value: '', cwd: '', path: '', paths: [] as FileDir[], cache: [] as FileDir[], visible: false, index: 0, create: false, inputCallbacks: {}, } type S = typeof state const absPath = (path = '') => path.startsWith('~') ? join($HOME, path.slice(1)) : path const validPath = async (path = '') => { if (!path) return '' const fullpath = absPath(path) return (await exists(fullpath)) ? fullpath : '' } const filterDirs = (filedirs: FileDir[]) => filedirs.filter((f) => f.dir) let listElRef: HTMLElement const resetState = { value: '', path: '', visible: false, index: 0 } const WhyDiv = (props: any) => <div {...props}>{props.children}</div> const feather = require('feather-icons') const ChangeProject = ({ create, value, visible, path, paths, index, inputCallbacks, }: S) => ( <Plugin visible={visible}> <Input {...inputCallbacks} id={'change-project-input'} focus={true} value={value} icon={feather.icons['home'].toSvg()} desc={create ? 'create new vim session with project' : 'change project'} /> <RowImportant active={/* TODO(smolck): Correct value? */ false}> {pathRelativeToHome(path)} </RowImportant> <WhyDiv onComponentDidMount={(e: HTMLElement) => { if (e) listElRef = e }} style={{ 'max-height': '50vh', 'overflow-y': 'hidden' }} > {paths.map(({ name }, ix) => ( <RowNormal key={name} active={ix === index}> <span>{name}</span> </RowNormal> ))} </WhyDiv> </Plugin> ) const container = document.createElement('div') container.id = 'change-project-container' document.getElementById('plugins')!.appendChild(container) const assignStateAndRender = (newState: any) => ( Object.assign(state, newState), render(<ChangeProject {...state} />, container) ) const show = ({ paths, path, cwd, create }: any) => ( vimBlur(), assignStateAndRender({ path, paths, create, cwd: cwd || state.cwd, index: 0, value: '', visible: true, cache: paths, }) ) state.inputCallbacks = { select: () => { vimFocus() if (!state.paths.length) { assignStateAndRender(resetState) return } const { name } = state.paths[state.index] if (!name) return const dirpath = join(state.path, name) state.create ? createVim(name, dirpath) : api.nvim.cmd(`cd ${dirpath}`) assignStateAndRender(resetState) }, change: (value: string) => assignStateAndRender({ value, index: 0, paths: value ? filterDirs(filter(state.paths, value, { key: 'name' })) : state.cache, }), tab: () => { if (!state.paths.length) { assignStateAndRender(resetState) return } const { name } = state.paths[state.index] if (!name) return const path = join(state.path, name) getDirFiles(path).then((paths) => show({ path, paths: filterDirs(paths) })) }, jumpNext: () => { const { name, dir } = state.paths[state.index] if (!dir) return const path = join(state.path, name) getDirFiles(path).then((paths) => show({ path, paths: filterDirs(paths) })) }, jumpPrev: () => { const next = state.path.split(sep) next.pop() const path = join(sep, ...next) getDirFiles(path).then((paths) => show({ path, paths: filterDirs(paths) })) }, // TODO: be more precise than this? also depends on scaled devices down: () => { listElRef.scrollTop += 300 assignStateAndRender({ index: Math.min(state.index + 17, state.paths.length - 1), }) }, up: () => { listElRef.scrollTop -= 300 assignStateAndRender({ index: Math.max(state.index - 17, 0) }) }, top: () => { listElRef.scrollTop = 0 }, bottom: () => { listElRef.scrollTop = listElRef.scrollHeight }, hide: () => (vimFocus(), assignStateAndRender(resetState)), next: () => assignStateAndRender({ index: state.index + 1 >= state.paths.length ? 0 : state.index + 1, }), prev: () => assignStateAndRender({ index: state.index - 1 < 0 ? state.paths.length - 1 : state.index - 1, }), } const go = async (userPath: string, create = false) => { const cwd = (await validPath(userPath)) || api.nvim.state.cwd const filedirs = await getDirFiles(cwd) const paths = filterDirs(filedirs) show({ paths, cwd, path: cwd, create }) } api.onAction('change-dir', (path = '') => go(path, false)) api.onAction('vim-create-dir', (path = '') => go(path, true)) api.nvim.watchState.cwd((cwd: string) => { if (cwd && homedir() !== cwd) renameCurrentToCwd(basename(cwd)) }) export const changeDir = () => go('', false) export const createInstanceWithDir = () => go('', true)