Repository: nvim-lua/plenary.nvim (lua)

Issue:
filetype.detect spend a lot of time when file is a big binary
https://github.com/nvim-telescope/telescope.nvim/issues/1637


when I use Telescope find_files select a big binary file, `filetype.detect` spend a lot of time and freezes nvim.

```
Conni2461 :
For me it spends 20 seconds in filetype detect (so a plenary issue). Because it reads the head (first line) of the file and looks for a shebang.

Better idea. Lets not read until \n. Lets just read the first n bytes (128 or 256) and then check for a shebang
```

`test file`
[ccls_test_telescop.zip](https://github.com/nvim-lua/plenary.nvim/files/7901615/ccls_test_telescop.zip)

============================================================================
update[01/21]
block in  `filetype.detect_from_modeline` (filetype.lua +172)
if i disable this check, work fine.

Pull request description:
Filetype detection used to read the first line for shebang and the last
line for modeline detection. This was really slow with large binary
files that don't contain newlines.

Add Path:readbyterange(offset, length) to allow reading a specific range
and reimplement detect_from_modeline and detect_from_shebang to work
with the first 256 and last 256 bytes of a file.

Fixes #307 and closes #322.

Patch:
diff --git a/lua/plenary/filetype.lua b/lua/plenary/filetype.lua
index ef451029..60fc743b 100644
--- a/lua/plenary/filetype.lua
+++ b/lua/plenary/filetype.lua
@@ -130,19 +130,21 @@ filetype.detect_from_name = function(filepath)
 end
 
 filetype.detect_from_modeline = function(filepath)
-  local tail = Path:new(filepath):tail(1)
+  local tail = Path:new(filepath):readbyterange(-256, 256)
   if not tail then
     return ""
   end
-  return filetype._parse_modeline(tail)
+  local lines = vim.split(tail, "\n")
+  return filetype._parse_modeline(lines[#lines])
 end
 
 filetype.detect_from_shebang = function(filepath)
-  local head = Path:new(filepath):head(1)
+  local head = Path:new(filepath):readbyterange(0, 256)
   if not head then
     return ""
   end
-  return filetype._parse_shebang(head)
+  local lines = vim.split("", "\n")
+  return filetype._parse_shebang(lines[1])
 end
 
 --- Detect a filetype from a path.
diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua
index 7da6912f..89292bca 100644
--- a/lua/plenary/path.lua
+++ b/lua/plenary/path.lua
@@ -844,4 +844,36 @@ function Path:iter()
   end
 end
 
+function Path:readbyterange(offset, length)
+  self = check_self(self)
+
+  local fd = uv.fs_open(self:expand(), "r", 438)
+  if not fd then
+    return
+  end
+  local stat = assert(uv.fs_fstat(fd))
+  if stat.type ~= "file" then
+    uv.fs_close(fd)
+    return nil
+  end
+
+  if offset < 0 then
+    offset = stat.size + offset
+  end
+
+  data = ""
+  while #data < length do
+    local read_chunk = assert(uv.fs_read(fd, length - #data, offset))
+    if #read_chunk == 0 then
+      break
+    end
+    data = data .. read_chunk
+    offset = offset + #read_chunk
+  end
+
+  assert(uv.fs_close(fd))
+
+  return data
+end
+
 return Path


Test patch:
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index be9cae1c..026feea3 100644
--- a/tests/plenary/path_spec.lua
+++ b/tests/plenary/path_spec.lua
@@ -684,6 +684,22 @@ SOFTWARE.]]
       assert.are.same(should, data)
     end)
   end)
+
+  describe("readbyterange", function()
+    it("should read bytes at given offset", function()
+      local p = Path:new "LICENSE"
+      local data = p:readbyterange(13, 10)
+      local should = "Copyright "
+      assert.are.same(should, data)
+    end)
+
+    it("supports negative offset", function()
+      local p = Path:new "LICENSE"
+      local data = p:readbyterange(-10, 10)
+      local should = "SOFTWARE.\n"
+      assert.are.same(should, data)
+    end)
+  end)
 end)
 
 -- function TestPath:testIsDir()