aboutsummaryrefslogtreecommitdiffhomepage
path: root/.config/nvim/after/ftplugin/php.lua
blob: 4935a63a670398e02006bc59a4ce8f7f0712c487 (plain)
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
vimrc.after_ftplugin('php', function(conf)
   -- Find "composer.json" file upward from the base directory.
   local function find_composer_json(base_dir)
      return vim.fs.find('composer.json', {
         path = base_dir,
         upward = true,
         stop = vim.loop.os_homedir(),
         type = 'file',
      })[1]
   end

   -- Read and decode JSON file.
   local function load_json(file_path)
      local ok_read, content = pcall(vim.fn.readblob, file_path)
      if not ok_read then
         return nil
      end
      local ok_decode, obj = pcall(vim.json.decode, content)
      if not ok_decode then
         return nil
      end
      return obj
   end

   local function normalize_psr4(psr4_namespace, psr4_dir)
      if type(psr4_dir) == 'table' then
         if #psr4_dir == 1 then
            psr4_dir = psr4_dir[1]
         else
            return nil -- psr-4 section is ambiguous
         end
      end
      if type(psr4_namespace) ~= 'string' or type(psr4_dir) ~= 'string' then
         return nil -- psr-4 section is invalid
      end
      if psr4_namespace:sub(-1, -1) == '\\' then
         psr4_namespace = psr4_namespace:sub(0, -2)
      end
      if psr4_dir:sub(-1, -1) == '/' then
         psr4_dir = psr4_dir:sub(0, -2)
      end
      return psr4_namespace, psr4_dir
   end

   -- Generate namespace declaration based on the current file name and autoload settings.
   local function generate_namespace_declaration()
      local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
      local path_to_composer_json = find_composer_json(current_dir)
      if not path_to_composer_json then
         return nil -- failed to locate composer.json
      end
      local composer_json = load_json(path_to_composer_json)
      if not composer_json then
         return nil -- failed to load composer.json
      end
      local psr4 = vim.tbl_get(composer_json, 'autoload', 'psr-4')
      if not psr4 then
         return nil -- autoload.psr-4 section is absent
      end
      for psr4_namespace, psr4_dir in pairs(psr4) do
         psr4_namespace, psr4_dir = normalize_psr4(psr4_namespace, psr4_dir)
         if psr4_namespace and psr4_dir then
            local namespace_root_dir = vim.fs.dirname(path_to_composer_json) .. '/' .. psr4_dir
            if vim.startswith(current_dir, namespace_root_dir) then
               local current_path_suffix = current_dir:sub(#namespace_root_dir + 1)
               local namespace = psr4_namespace .. current_path_suffix:gsub('/', '\\')
               return ("namespace %s;"):format(namespace)
            end
         end
      end
      return nil -- failed to detect namespace
   end

   local function generate_template()
      local lines = {
         '<?php',
         '',
         'declare(strict_types=1);',
         '',
      }
      local namespace_decl = generate_namespace_declaration()
      if namespace_decl then
         lines[#lines + 1] = namespace_decl
         lines[#lines + 1] = ''
      end
      lines[#lines + 1] = ''
      return lines
   end

   -- If a buffer is empty, insert a template and position the
   -- cursor at the end of the buffer.
   --
   -- Example 1:
   -- <?php
   --
   -- declare(strict_types=1);
   --
   -- [cursor]
   --
   -- Example 2 with namespace declaration:
   -- <?php
   --
   -- declare(strict_types=1);
   --
   -- namespace Foo\Bar\Baz;
   --
   -- [cursor]
   if vim.fn.line('$') == 1 and vim.fn.getline(1) == '' then
      vim.fn.setline(1, generate_template())
      vim.fn.cursor('$', 0)
   end
end)