1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
local micro = import("micro")
local buffer = import("micro/buffer")
local filepath = import("filepath")
local os = import("os")

function findWordStart(line, cursorX)
  local pos = cursorX
  -- First, find the basic word boundary
  while pos > 1 and not line:sub(pos-1, pos-1):match("%s") do
    pos = pos - 1
  end

  -- Check if this word contains a path starting with /
  local wordEnd = cursorX
  while wordEnd <= #line and not line:sub(wordEnd, wordEnd):match("%s") do
    wordEnd = wordEnd + 1
  end
  local word = line:sub(pos, wordEnd - 1)

  -- If word contains /, find the / and expand backwards past non-letters
  local slashPos = word:find("/")
  if slashPos then
    local actualStart = pos + slashPos - 1
    -- Go backwards from the / past any non-letter characters
    while actualStart > 1 and not line:sub(actualStart-1, actualStart-1):match("%a") and not line:sub(actualStart-1, actualStart-1):match("%s") do
      actualStart = actualStart - 1
    end
    return actualStart
  end

  return pos
end

function findWordEnd(line, cursorX)
  local pos = cursorX
  while pos <= #line and not line:sub(pos, pos):match("%s") do
    pos = pos + 1
  end
  return pos - 1
end

function OpenTabFromBuffer(bp, name, buf)
  if not buf then
    micro.InfoBar():Error("Buffer is nil")
    return nil
  end

  buf:SetName(name)
  
  -- Create a new empty tab
  bp:NewTabCmd(nil)
  
  -- Get the newly created tab (it's the last one and now active)
  local tabs = micro.Tabs()
  local newTab = tabs.List[#tabs.List]
  
  -- Replace the buffer in the new tab's first pane with our buffer
  if newTab and newTab.Panes and newTab.Panes[1] then
    newTab.Panes[1]:OpenBuffer(buf)
  end

  return newTab
end

function OpenTabFromFile(bp, filePath, lineNum)
  -- TODO: Check whether Micro supports multiple views on the same buffer
  -- If it does, we might want to allow opening the same file in multiple tabs/splits

  -- First check if this file is already open in an existing tab
  local tabs = micro.Tabs()
  for i = 0, #tabs.List - 1 do
    local tab = tabs.List[i + 1] -- Lua is 1-indexed
    if tab and tab.Panes and tab.Panes[1] and tab.Panes[1].Buf then
      local buf = tab.Panes[1].Buf
      if buf.Path == filePath then
        -- File is already open, reload it from disk before jumping to position
        -- This ensures we see the latest content if Claude edited the file
        buf:ReOpen()

        -- Switch to that tab and position cursor
        tabs:SetActive(i)
        if lineNum and lineNum > 0 then
          local pane = tab.Panes[1]
          if pane and pane.Cursor then
            pane.Cursor:GotoLoc(buffer.Loc(0, lineNum - 1)) -- Convert to 0-based
            -- Relocate the view to center the cursor
            pane:Relocate()
          end
        end
        return tab
      end
    end
  end

  -- File not open, create new buffer and tab
  local newBuf, err = buffer.NewBufferFromFile(filePath)
  if err ~= nil then
    return nil
  end

  local newTab = OpenTabFromBuffer(bp, filePath, newBuf)

  -- Position cursor at specified line if provided
  if newTab and lineNum and lineNum > 0 then
    local pane = newTab.Panes[1]
    if pane and pane.Cursor then
      pane.Cursor:GotoLoc(buffer.Loc(0, lineNum - 1)) -- Convert to 0-based
      -- Relocate the view to center the cursor
      pane:Relocate()
    end
  end

  return newTab
end

function find_project_dir(startPath)
  local currentDir = startPath

  while currentDir ~= "/" do
    local gitPath = filepath.Join(currentDir, ".git")
    if fileExists(gitPath) then
      return currentDir
    end

    -- Go up one directory
    local parentDir = filepath.Dir(currentDir)
    if parentDir == currentDir then
      -- Reached root, stop
      break
    end
    currentDir = parentDir
  end

  return nil
end

function fileExists(path)
  local file = io.open(path, "r")
  if file then
    file:close()
    return true
  end
  return false
end