mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-20 23:49:50 +02:00
.github
.idea
.teamcity
annotation-processors
assets
doc
gradle
scripts
src
main
java
com
maddyhome
idea
vim
action
command
common
config
customization
ex
extension
group
copy
visual
ChangeGroup.kt
CommandGroup.kt
EditorGroup.java
FileGroup.kt
HistoryGroup.java
IjOptionProperties.kt
IjOptions.kt
IjStatisticsService.kt
IjVimPsiService.kt
IjVimRedrawService.kt
IjVimStorageService.kt
IjVimSystemInfoService.kt
KeyGroup.java
LastTabService.kt
MacroGroup.kt
MotionGroup.kt
NotificationService.kt
OptionGroup.kt
ProcessGroup.kt
RegisterGroup.java
ScrollGroup.kt
SystemMarks.kt
TabServiceImpl.kt
VimJumpServiceImpl.kt
VimMarkServiceImpl.kt
WindowGroup.java
XMLGroup.kt
handler
helper
icons
ide
inspections
key
listener
mark
newapi
statistic
troubleshooting
ui
vimscript
EventFacade.java
PluginStartup.kt
RegisterActions.kt
VimBundledDictionaryProvider.kt
VimPlugin.java
VimProjectService.kt
VimTypedActionHandler.kt
resources
test
testFixtures
tests
vim-engine
vimscript-info
.editorconfig
.gitignore
AUTHORS.md
CHANGES.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE.txt
README.md
ThirdPartyLicenses.md
build.gradle.kts
gradle.properties
gradlew
gradlew.bat
qodana.sarif.json
qodana.yaml
settings.gradle.kts

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
|
|
}
|
|
}
|
|
}
|
|
}
|