mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-29 01:34:10 +02:00

Simplifies and fixes counting words, especially when selection intersects words. Also moves implementation to engine and adds more tests.
354 lines
12 KiB
Kotlin
354 lines
12 KiB
Kotlin
/*
|
|
* Copyright 2003-2023 The IdeaVim authors
|
|
*
|
|
* Use of this source code is governed by an MIT-style
|
|
* license that can be found in the LICENSE.txt file or at
|
|
* https://opensource.org/licenses/MIT.
|
|
*/
|
|
package com.maddyhome.idea.vim.group
|
|
|
|
import com.intellij.openapi.actionSystem.DataContext
|
|
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
|
import com.intellij.openapi.application.ApplicationManager
|
|
import com.intellij.openapi.diagnostic.Logger
|
|
import com.intellij.openapi.editor.Editor
|
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
|
import com.intellij.openapi.fileEditor.TextEditor
|
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
|
import com.intellij.openapi.fileEditor.impl.EditorsSplitters
|
|
import com.intellij.openapi.fileTypes.FileTypeManager
|
|
import com.intellij.openapi.project.Project
|
|
import com.intellij.openapi.project.ProjectManager
|
|
import com.intellij.openapi.roots.ProjectRootManager
|
|
import com.intellij.openapi.vfs.LocalFileSystem
|
|
import com.intellij.openapi.vfs.VirtualFile
|
|
import com.intellij.openapi.vfs.VirtualFileManager
|
|
import com.intellij.psi.search.FilenameIndex
|
|
import com.intellij.psi.search.ProjectScope
|
|
import com.maddyhome.idea.vim.VimPlugin
|
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
|
import com.maddyhome.idea.vim.api.VimEditor
|
|
import com.maddyhome.idea.vim.api.VimFileBase
|
|
import com.maddyhome.idea.vim.api.injector
|
|
import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
|
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
|
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
|
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
|
import com.maddyhome.idea.vim.newapi.execute
|
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
|
import java.io.File
|
|
import java.util.*
|
|
|
|
class FileGroup : VimFileBase() {
|
|
override fun openFile(filename: String, context: ExecutionContext): Boolean {
|
|
if (logger.isDebugEnabled) {
|
|
logger.debug("openFile($filename)")
|
|
}
|
|
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context)
|
|
?: return false // API change - don't merge
|
|
|
|
val found = findFile(filename, project)
|
|
|
|
if (found != null) {
|
|
if (logger.isDebugEnabled) {
|
|
logger.debug("found file: $found")
|
|
}
|
|
// Can't open a file unless it has a known file type. The next call will return the known type.
|
|
// If unknown, IDEA will prompt the user to pick a type.
|
|
val type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(found, project)
|
|
|
|
if (type != null) {
|
|
val fem = FileEditorManager.getInstance(project)
|
|
fem.openFile(found, true)
|
|
|
|
return true
|
|
} else {
|
|
// There was no type and user didn't pick one. Don't open the file
|
|
// Return true here because we found the file but the user canceled by not picking a type.
|
|
return true
|
|
}
|
|
} else {
|
|
VimPlugin.showMessage(message("unable.to.find.0", filename))
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
fun findFile(filename: String, project: Project): VirtualFile? {
|
|
var found: VirtualFile?
|
|
// Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
|
|
// it only supports forward slash on Unix (tested on Mac)
|
|
// VFS works with both directory separators (tested on Mac and Windows)
|
|
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
|
val relativePath = filename.substring(2)
|
|
val dir = System.getProperty("user.home")
|
|
if (logger.isDebugEnabled) {
|
|
logger.debug("home dir file")
|
|
logger.debug("looking for $relativePath in $dir")
|
|
}
|
|
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(dir, relativePath))
|
|
} else {
|
|
found = LocalFileSystem.getInstance().findFileByIoFile(File(filename))
|
|
|
|
if (found == null) {
|
|
found = findByNameInContentRoots(filename, project)
|
|
if (found == null) {
|
|
found = findByNameInProject(filename, project)
|
|
}
|
|
}
|
|
}
|
|
|
|
return found
|
|
}
|
|
|
|
private fun findByNameInContentRoots(filename: String, project: Project): VirtualFile? {
|
|
var found: VirtualFile? = null
|
|
val prm = ProjectRootManager.getInstance(project)
|
|
val roots = prm.contentRoots
|
|
for (i in roots.indices) {
|
|
if (logger.isDebugEnabled) {
|
|
logger.debug("root[" + i + "] = " + roots[i].path)
|
|
}
|
|
found = roots[i].findFileByRelativePath(filename)
|
|
if (found != null) {
|
|
break
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
/**
|
|
* Closes the current editor.
|
|
*/
|
|
override fun closeFile(editor: VimEditor, context: ExecutionContext) {
|
|
val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext))
|
|
if (project != null) {
|
|
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
|
|
val window = fileEditorManager.currentWindow
|
|
val virtualFile = fileEditorManager.currentFile
|
|
|
|
if (virtualFile != null && window != null) {
|
|
// During the work on VIM-2912 I've changed the close function to this one.
|
|
// However, the function with manager seems to work weirdly and it causes VIM-2953
|
|
//window.getManager().closeFile(virtualFile, true, false);
|
|
window.closeFile(virtualFile)
|
|
|
|
// Get focus after closing tab
|
|
window.requestFocus(true)
|
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
|
// This thing doesn't have an implementation in test mode
|
|
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes editor.
|
|
*/
|
|
override fun closeFile(number: Int, context: ExecutionContext) {
|
|
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
|
|
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
|
|
val window = fileEditorManager.currentWindow
|
|
val editors = fileEditorManager.openFiles
|
|
if (window != null) {
|
|
if (number >= 0 && number < editors.size) {
|
|
fileEditorManager.closeFile(editors[number], window)
|
|
}
|
|
}
|
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
|
// This thing doesn't have an implementation in test mode
|
|
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves specific file in the project.
|
|
*/
|
|
override fun saveFile(editor: VimEditor, context: ExecutionContext) {
|
|
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
|
|
injector.nativeActionManager.saveAll
|
|
} else {
|
|
injector.nativeActionManager.saveCurrent
|
|
}
|
|
action.execute(editor, context)
|
|
}
|
|
|
|
/**
|
|
* Saves all files in the project.
|
|
*/
|
|
override fun saveFiles(editor: VimEditor, context: ExecutionContext) {
|
|
injector.nativeActionManager.saveAll.execute(editor, context)
|
|
}
|
|
|
|
/**
|
|
* Selects then next or previous editor.
|
|
*/
|
|
override fun selectFile(count: Int, context: ExecutionContext): Boolean {
|
|
var count = count
|
|
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return false
|
|
val fem = FileEditorManager.getInstance(project) // API change - don't merge
|
|
val editors = fem.openFiles
|
|
if (count == 99) {
|
|
count = editors.size - 1
|
|
}
|
|
if (count < 0 || count >= editors.size) {
|
|
return false
|
|
}
|
|
|
|
fem.openFile(editors[count], true)
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Selects then next or previous editor.
|
|
*/
|
|
override fun selectNextFile(count: Int, context: ExecutionContext) {
|
|
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
|
|
val fem = FileEditorManager.getInstance(project) // API change - don't merge
|
|
val editors = fem.openFiles
|
|
val current = fem.selectedFiles[0]
|
|
for (i in editors.indices) {
|
|
if (editors[i] == current) {
|
|
val pos = (i + (count % editors.size) + editors.size) % editors.size
|
|
|
|
fem.openFile(editors[pos], true)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects previous editor tab.
|
|
*/
|
|
override fun selectPreviousTab(context: ExecutionContext) {
|
|
val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext)) ?: return
|
|
val vf = getInstance(project).lastTab
|
|
if (vf != null && vf.isValid) {
|
|
FileEditorManager.getInstance(project).openFile(vf, true)
|
|
} else {
|
|
VimPlugin.indicateError()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the previous tab.
|
|
*/
|
|
fun getPreviousTab(context: DataContext): VirtualFile? {
|
|
val project = PlatformDataKeys.PROJECT.getData(context) ?: return null
|
|
val vf = getInstance(project).lastTab
|
|
if (vf != null && vf.isValid) {
|
|
return vf
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun selectEditor(project: Project, file: VirtualFile): Editor? {
|
|
val fMgr = FileEditorManager.getInstance(project)
|
|
val feditors = fMgr.openFile(file, true)
|
|
if (feditors.size > 0) {
|
|
if (feditors[0] is TextEditor) {
|
|
val editor = (feditors[0] as TextEditor).editor
|
|
if (!editor.isDisposed) {
|
|
return editor
|
|
}
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
|
|
val editor = (vimEditor as IjVimEditor).editor
|
|
val msg = StringBuilder()
|
|
val vf = EditorHelper.getVirtualFile(editor)
|
|
if (vf != null) {
|
|
msg.append('"')
|
|
if (fullPath) {
|
|
msg.append(vf.path)
|
|
} else {
|
|
val project = editor.project
|
|
if (project != null) {
|
|
val root = ProjectRootManager.getInstance(project).fileIndex.getContentRootForFile(vf)
|
|
if (root != null) {
|
|
msg.append(vf.path.substring(root.path.length + 1))
|
|
} else {
|
|
msg.append(vf.path)
|
|
}
|
|
}
|
|
}
|
|
msg.append("\" ")
|
|
} else {
|
|
msg.append("\"[No File]\" ")
|
|
}
|
|
|
|
val doc = editor.document
|
|
if (!doc.isWritable) {
|
|
msg.append("[RO] ")
|
|
} else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
|
|
msg.append("[+] ")
|
|
}
|
|
|
|
val lline = editor.caretModel.logicalPosition.line
|
|
val total = IjVimEditor(editor).lineCount()
|
|
val pct = (lline.toFloat() / total.toFloat() * 100f + 0.5).toInt()
|
|
|
|
msg.append("line ").append(lline + 1).append(" of ").append(total)
|
|
msg.append(" --").append(pct).append("%-- ")
|
|
|
|
val lp = editor.caretModel.logicalPosition
|
|
val col = editor.caretModel.offset - doc.getLineStartOffset(lline)
|
|
|
|
msg.append("col ").append(col + 1)
|
|
if (col != lp.column) {
|
|
msg.append("-").append(lp.column + 1)
|
|
}
|
|
|
|
VimPlugin.showMessage(msg.toString())
|
|
}
|
|
|
|
override fun selectEditor(projectId: String, documentPath: String, protocol: String?): VimEditor? {
|
|
val fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol) ?: return null
|
|
val virtualFile = fileSystem.findFileByPath(documentPath) ?: return null
|
|
|
|
val project = Arrays.stream(ProjectManager.getInstance().openProjects)
|
|
.filter { p: Project? -> injector.file.getProjectId(p!!) == projectId }
|
|
.findFirst().orElseThrow()
|
|
|
|
val editor = selectEditor(project, virtualFile) ?: return null
|
|
return IjVimEditor(editor)
|
|
}
|
|
|
|
override fun getProjectId(project: Any): String {
|
|
require(project is Project)
|
|
return project.name + "-" + project.locationHash
|
|
}
|
|
|
|
companion object {
|
|
private fun findByNameInProject(filename: String, project: Project): VirtualFile? {
|
|
val projectScope = ProjectScope.getProjectScope(project)
|
|
val names = FilenameIndex.getVirtualFilesByName(filename, projectScope)
|
|
if (!names.isEmpty()) {
|
|
return names.stream().findFirst().get()
|
|
}
|
|
return null
|
|
}
|
|
|
|
private val logger = Logger.getInstance(
|
|
FileGroup::class.java.name
|
|
)
|
|
|
|
/**
|
|
* Respond to editor tab selection and remember the last used tab
|
|
*/
|
|
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
|
if (event.oldFile != null) {
|
|
getInstance(event.manager.project).lastTab = event.oldFile
|
|
}
|
|
}
|
|
}
|
|
}
|