mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-28 16:34:10 +02:00
VIM-1639 Ctrl-o and Ctrl-i jumping in files of different projects
This commit is contained in:
parent
a9ba9789fd
commit
06ef1c1182
src/main/java/com/maddyhome/idea/vim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim
api
vimscript/model/commands
@ -33,6 +33,8 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimMotionGroupBase
|
||||
import com.maddyhome.idea.vim.api.addJump
|
||||
import com.maddyhome.idea.vim.api.anyNonWhitespace
|
||||
import com.maddyhome.idea.vim.api.getJump
|
||||
import com.maddyhome.idea.vim.api.getJumpSpot
|
||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||
import com.maddyhome.idea.vim.api.getVisualLineCount
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
@ -163,8 +165,8 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
|
||||
override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
|
||||
val jumpService = injector.jumpService
|
||||
val spot = jumpService.getJumpSpot()
|
||||
val (line, col, fileName) = jumpService.getJump(count) ?: return Motion.Error
|
||||
val spot = jumpService.getJumpSpot(editor)
|
||||
val (line, col, fileName) = jumpService.getJump(editor, count) ?: return Motion.Error
|
||||
val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error
|
||||
val lp = BufferPosition(line, col, false)
|
||||
val lpNative = LogicalPosition(line, col, false)
|
||||
|
@ -15,6 +15,7 @@ import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
|
||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.PlaceInfo
|
||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.RecentPlacesListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimJumpServiceBase
|
||||
@ -26,6 +27,7 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import org.jdom.Element
|
||||
|
||||
|
||||
@State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
|
||||
internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateComponent<Element?> {
|
||||
companion object {
|
||||
@ -40,60 +42,72 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
|
||||
}
|
||||
}
|
||||
|
||||
// We do not delete old project records.
|
||||
// Rationale: It's more likely that users will want to review their old projects and access their jump history
|
||||
// (e.g., recent files), than for the 100 jumps (max number of records) to consume enough space to be noticeable.
|
||||
override fun getState(): Element {
|
||||
val jumpsElem = Element("jumps")
|
||||
for (jump in jumps) {
|
||||
val jumpElem = Element("jump")
|
||||
jumpElem.setAttribute("line", jump.line.toString())
|
||||
jumpElem.setAttribute("column", jump.col.toString())
|
||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||
jumpsElem.addContent(jumpElem)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("saved jump = $jump")
|
||||
val projectsElem = Element("projects")
|
||||
for ((project, jumps) in projectToJumps) {
|
||||
val projectElement = Element("project").setAttribute("id", project)
|
||||
for (jump in jumps) {
|
||||
val jumpElem = Element("jump")
|
||||
jumpElem.setAttribute("line", jump.line.toString())
|
||||
jumpElem.setAttribute("column", jump.col.toString())
|
||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||
projectElement.addContent(jumpElem)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("saved jump = $jump")
|
||||
}
|
||||
}
|
||||
projectsElem.addContent(projectElement)
|
||||
}
|
||||
return jumpsElem
|
||||
return projectsElem
|
||||
}
|
||||
|
||||
override fun loadState(state: Element) {
|
||||
val jumpList = state.getChildren("jump")
|
||||
for (jumpElement in jumpList) {
|
||||
val jump = Jump(
|
||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||
jumpElement.getAttributeValue("filename"),
|
||||
)
|
||||
jumps.add(jump)
|
||||
}
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("jumps=$jumps")
|
||||
val projectElements = state.getChildren("project")
|
||||
for (projectElement in projectElements) {
|
||||
val jumps = mutableListOf<Jump>()
|
||||
val jumpElements = projectElement.getChildren("jump")
|
||||
for (jumpElement in jumpElements) {
|
||||
val jump = Jump(
|
||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||
jumpElement.getAttributeValue("filename"),
|
||||
)
|
||||
jumps.add(jump)
|
||||
}
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("jumps=$jumps")
|
||||
}
|
||||
val projectId = projectElement.getAttributeValue("id")
|
||||
projectToJumps[projectId] = jumps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class JumpsListener : RecentPlacesListener {
|
||||
internal class JumpsListener(val project: Project) : RecentPlacesListener {
|
||||
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
|
||||
if (!injector.globalIjOptions().unifyjumps) return
|
||||
|
||||
|
||||
val jumpService = injector.jumpService
|
||||
if (!isChanged) {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.addJump(jump, true)
|
||||
jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun recentPlaceRemoved(changePlace: PlaceInfo, isChanged: Boolean) {
|
||||
if (!injector.globalIjOptions().unifyjumps) return
|
||||
|
||||
|
||||
val jumpService = injector.jumpService
|
||||
if (!isChanged) {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.removeJump(jump)
|
||||
jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,11 @@ import java.lang.System.identityHashCode
|
||||
|
||||
@ApiStatus.Internal
|
||||
internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
companion object {
|
||||
// For cases where Editor does not have a project (for some reason)
|
||||
// It's something IJ Platform related and stored here because of this reason
|
||||
const val DEFAULT_PROJECT_ID = "no project"
|
||||
}
|
||||
|
||||
// All the editor actions should be performed with top level editor!!!
|
||||
// Be careful: all the EditorActionHandler implementation should correctly process InjectedEditors
|
||||
@ -369,6 +374,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) }
|
||||
}
|
||||
|
||||
override val projectId = editor.project?.basePath ?: DEFAULT_PROJECT_ID
|
||||
|
||||
override fun visualPositionToOffset(position: VimVisualPosition): Offset {
|
||||
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)).offset
|
||||
}
|
||||
|
@ -242,6 +242,9 @@ public interface VimEditor {
|
||||
|
||||
public fun getPath(): String?
|
||||
public fun extractProtocol(): String?
|
||||
|
||||
// Can be used as a key to store something for specific project
|
||||
public val projectId: String
|
||||
|
||||
public fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments)
|
||||
public fun exitSelectModeNative(adjustCaret: Boolean)
|
||||
|
@ -9,9 +9,14 @@
|
||||
package com.maddyhome.idea.vim.api
|
||||
|
||||
import com.maddyhome.idea.vim.mark.Jump
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
// todo should it be multicaret?
|
||||
// todo docs
|
||||
// todo it would be better to have some Vim scope for this purpose (p:), to store things project-wise like for buffers
|
||||
/**
|
||||
* This service manages jump lists for different projects
|
||||
*/
|
||||
public interface VimJumpService {
|
||||
/**
|
||||
* Timestamp (`System.currentTimeMillis()`) of the last Jump command <C-o>, <C-i>
|
||||
@ -19,17 +24,23 @@ public interface VimJumpService {
|
||||
* and messes up our jump list
|
||||
*/
|
||||
public var lastJumpTimeStamp: Long
|
||||
|
||||
public fun includeCurrentCommandAsNavigation(editor: VimEditor)
|
||||
public fun getJumpSpot(): Int
|
||||
public fun getJump(count: Int): Jump?
|
||||
public fun getJumps(): List<Jump>
|
||||
public fun addJump(jump: Jump, reset: Boolean)
|
||||
|
||||
public fun getJump(projectId: String, count: Int): Jump?
|
||||
public fun getJumps(projectId: String): List<Jump>
|
||||
public fun getJumpSpot(projectId: String): Int
|
||||
|
||||
public fun addJump(projectId: String, jump: Jump, reset: Boolean)
|
||||
public fun saveJumpLocation(editor: VimEditor)
|
||||
public fun removeJump(jump: Jump)
|
||||
public fun dropLastJump()
|
||||
public fun updateJumpsFromInsert(editor: VimEditor, startOffset: Int, length: Int)
|
||||
public fun updateJumpsFromDelete(editor: VimEditor, startOffset: Int, length: Int)
|
||||
|
||||
public fun removeJump(projectId: String, jump: Jump)
|
||||
public fun dropLastJump(projectId: String)
|
||||
|
||||
public fun updateJumpsFromInsert(projectId: String, startOffset: Int, length: Int)
|
||||
public fun updateJumpsFromDelete(projectId: String, startOffset: Int, length: Int)
|
||||
|
||||
public fun includeCurrentCommandAsNavigation(editor: VimEditor)
|
||||
|
||||
@TestOnly
|
||||
public fun resetJumps()
|
||||
}
|
||||
|
||||
@ -37,5 +48,33 @@ public fun VimJumpService.addJump(editor: VimEditor, reset: Boolean) {
|
||||
val path = editor.getPath() ?: return
|
||||
val position = editor.offsetToBufferPosition(editor.currentCaret().offset.point)
|
||||
val jump = Jump(position.line, position.column, path)
|
||||
addJump(jump, reset)
|
||||
addJump(editor, jump, reset)
|
||||
}
|
||||
|
||||
public fun VimJumpService.getJump(editor: VimEditor, count: Int): Jump? {
|
||||
return getJump(editor.projectId, count)
|
||||
}
|
||||
public fun VimJumpService.getJumps(editor: VimEditor): List<Jump> {
|
||||
return getJumps(editor.projectId)
|
||||
}
|
||||
public fun VimJumpService.getJumpSpot(editor: VimEditor): Int {
|
||||
return getJumpSpot(editor.projectId)
|
||||
}
|
||||
|
||||
public fun VimJumpService.addJump(editor: VimEditor, jump: Jump, reset: Boolean) {
|
||||
return addJump(editor.projectId, jump, reset)
|
||||
}
|
||||
|
||||
public fun VimJumpService.removeJump(editor: VimEditor, jump: Jump) {
|
||||
return removeJump(editor.projectId, jump)
|
||||
}
|
||||
public fun VimJumpService.dropLastJump(editor: VimEditor) {
|
||||
return dropLastJump(editor.projectId)
|
||||
}
|
||||
|
||||
public fun VimJumpService.updateJumpsFromInsert(editor: VimEditor, startOffset: Int, length: Int) {
|
||||
return updateJumpsFromInsert(editor.projectId, startOffset, length)
|
||||
}
|
||||
public fun VimJumpService.updateJumpsFromDelete(editor: VimEditor, startOffset: Int, length: Int) {
|
||||
return updateJumpsFromDelete(editor.projectId, startOffset, length)
|
||||
}
|
||||
|
@ -11,51 +11,36 @@ package com.maddyhome.idea.vim.api
|
||||
import com.maddyhome.idea.vim.mark.Jump
|
||||
|
||||
public abstract class VimJumpServiceBase : VimJumpService {
|
||||
@JvmField
|
||||
protected val jumps: MutableList<Jump> = ArrayList() // todo should it be mutable?
|
||||
@JvmField
|
||||
protected var jumpSpot: Int = -1
|
||||
protected val projectToJumps: MutableMap<String, MutableList<Jump>> = mutableMapOf()
|
||||
protected val projectToJumpSpot: MutableMap<String, Int> = mutableMapOf()
|
||||
|
||||
override fun getJumpSpot(): Int {
|
||||
return jumpSpot
|
||||
}
|
||||
|
||||
override fun getJump(count: Int): Jump? {
|
||||
val index = jumps.size - 1 - (jumpSpot - count)
|
||||
return if (index < 0 || index >= jumps.size) {
|
||||
null
|
||||
} else {
|
||||
jumpSpot -= count
|
||||
jumps[index]
|
||||
override fun getJump(projectId: String, count: Int): Jump? {
|
||||
val jumps = projectToJumps[projectId] ?: mutableListOf()
|
||||
projectToJumpSpot.putIfAbsent(projectId, -1)
|
||||
val index = jumps.size - 1 - (projectToJumpSpot[projectId]!! - count)
|
||||
return jumps.getOrNull(index)?.also {
|
||||
projectToJumpSpot[projectId] = projectToJumpSpot[projectId]!! - count
|
||||
}
|
||||
}
|
||||
|
||||
override fun getJumps(): List<Jump> {
|
||||
return jumps
|
||||
override fun getJumps(projectId: String): List<Jump> {
|
||||
return projectToJumps[projectId] ?: emptyList()
|
||||
}
|
||||
|
||||
override fun addJump(jump: Jump, reset: Boolean) {
|
||||
override fun getJumpSpot(projectId: String): Int {
|
||||
return projectToJumpSpot[projectId] ?: -1
|
||||
}
|
||||
|
||||
override fun addJump(projectId: String, jump: Jump, reset: Boolean) {
|
||||
lastJumpTimeStamp = System.currentTimeMillis()
|
||||
val filename = jump.filepath
|
||||
|
||||
for (i in jumps.indices) {
|
||||
val j = jumps[i]
|
||||
if (filename == j.filepath && j.line == jump.line) {
|
||||
jumps.removeAt(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val jumps = projectToJumps.getOrPut(projectId) { mutableListOf() }
|
||||
jumps.removeIf { it.filepath == jump.filepath && it.line == jump.line }
|
||||
jumps.add(jump)
|
||||
|
||||
if (reset) {
|
||||
jumpSpot = -1
|
||||
} else {
|
||||
jumpSpot++
|
||||
}
|
||||
projectToJumpSpot[projectId] = if (reset) -1 else (projectToJumpSpot[projectId] ?: -1) + 1
|
||||
|
||||
if (jumps.size > SAVE_JUMP_COUNT) {
|
||||
jumps.removeAt(0)
|
||||
jumps.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,26 +50,25 @@ public abstract class VimJumpServiceBase : VimJumpService {
|
||||
includeCurrentCommandAsNavigation(editor)
|
||||
}
|
||||
|
||||
override fun removeJump(jump: Jump) {
|
||||
val lastIndex = jumps.withIndex().findLast { it.value == jump }?.index ?: return
|
||||
jumps.removeAt(lastIndex)
|
||||
override fun removeJump(projectId: String, jump: Jump) {
|
||||
projectToJumps[projectId]?.removeIf { it == jump }
|
||||
}
|
||||
|
||||
override fun dropLastJump() {
|
||||
jumps.removeLast()
|
||||
override fun dropLastJump(projectId: String) {
|
||||
projectToJumps[projectId]?.removeLastOrNull()
|
||||
}
|
||||
|
||||
override fun updateJumpsFromInsert(editor: VimEditor, startOffset: Int, length: Int) {
|
||||
override fun updateJumpsFromInsert(projectId: String, startOffset: Int, length: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun updateJumpsFromDelete(editor: VimEditor, startOffset: Int, length: Int) {
|
||||
override fun updateJumpsFromDelete(projectId: String, startOffset: Int, length: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun resetJumps() {
|
||||
jumps.clear()
|
||||
jumpSpot = -1
|
||||
projectToJumps.clear()
|
||||
projectToJumpSpot.clear()
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -46,10 +46,10 @@ public abstract class VimMarkServiceBase : VimMarkService {
|
||||
}
|
||||
|
||||
@JvmField
|
||||
protected val globalMarks: java.util.HashMap<Char, Mark> = HashMap()
|
||||
protected val globalMarks: HashMap<Char, Mark> = HashMap()
|
||||
|
||||
// marks are stored for primary caret only
|
||||
protected val filepathToLocalMarks: java.util.HashMap<String, LocalMarks<Char, Mark>> = HashMap()
|
||||
protected val filepathToLocalMarks: HashMap<String, LocalMarks<Char, Mark>> = HashMap()
|
||||
|
||||
public class LocalMarks<K, V> : HashMap<K, V>() {
|
||||
public var myTimestamp: Date = Date()
|
||||
@ -185,7 +185,7 @@ public abstract class VimMarkServiceBase : VimMarkService {
|
||||
if (caret.isPrimary) {
|
||||
if (mark.key == BEFORE_JUMP_MARK) {
|
||||
val jump = Jump(mark.line, mark.col, mark.filepath)
|
||||
injector.jumpService.addJump(jump, true)
|
||||
injector.jumpService.addJump(editor, jump, true)
|
||||
}
|
||||
getLocalMarks(mark.filepath)[markChar] = mark
|
||||
} else {
|
||||
|
@ -11,6 +11,8 @@ package com.maddyhome.idea.vim.vimscript.model.commands
|
||||
import com.intellij.vim.annotations.ExCommand
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getJumpSpot
|
||||
import com.maddyhome.idea.vim.api.getJumps
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||
@ -25,8 +27,8 @@ import kotlin.math.absoluteValue
|
||||
public data class JumpsCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges, argument) {
|
||||
override val argFlags: CommandHandlerFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_FORBIDDEN, Access.READ_ONLY)
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
val jumps = injector.jumpService.getJumps()
|
||||
val spot = injector.jumpService.getJumpSpot()
|
||||
val jumps = injector.jumpService.getJumps(editor)
|
||||
val spot = injector.jumpService.getJumpSpot(editor)
|
||||
|
||||
val text = StringBuilder(" jump line col file/text\n")
|
||||
jumps.forEachIndexed { idx, jump ->
|
||||
|
Loading…
Reference in New Issue
Block a user