1 #include "buffer_utils.hh"
2 
3 #include "buffer_manager.hh"
4 #include "event_manager.hh"
5 #include "file.hh"
6 #include "selection.hh"
7 #include "changes.hh"
8 
9 #include <unistd.h>
10 
11 #if defined(__APPLE__)
12 #define st_mtim st_mtimespec
13 #endif
14 
15 
16 namespace Kakoune
17 {
18 
replace(Buffer & buffer,ArrayView<BufferRange> ranges,ConstArrayView<String> strings)19 void replace(Buffer& buffer, ArrayView<BufferRange> ranges, ConstArrayView<String> strings)
20 {
21     ForwardChangesTracker changes_tracker;
22     size_t timestamp  = buffer.timestamp();
23     for (size_t index = 0; index < ranges.size(); ++index)
24     {
25         auto& range = ranges[index];
26         range.begin = changes_tracker.get_new_coord_tolerant(range.begin);
27         range.end = changes_tracker.get_new_coord_tolerant(range.end);
28         kak_assert(buffer.is_valid(range.begin) and buffer.is_valid(range.end));
29 
30         range = buffer.replace(range.begin, range.end, strings.empty() ? StringView{} : strings[std::min(index, strings.size()-1)]);
31         kak_assert(buffer.is_valid(range.begin) and buffer.is_valid(range.end));
32         changes_tracker.update(buffer, timestamp);
33     }
34 
35     buffer.check_invariant();
36 }
37 
get_column(const Buffer & buffer,ColumnCount tabstop,BufferCoord coord)38 ColumnCount get_column(const Buffer& buffer,
39                        ColumnCount tabstop, BufferCoord coord)
40 {
41     auto line = buffer[coord.line];
42     auto col = 0_col;
43     for (auto it = line.begin();
44          it != line.end() and coord.column > (int)(it - line.begin()); )
45     {
46         if (*it == '\t')
47         {
48             col = (col / tabstop + 1) * tabstop;
49             ++it;
50         }
51         else
52             col += codepoint_width(utf8::read_codepoint(it, line.end()));
53     }
54     return col;
55 }
56 
column_length(const Buffer & buffer,ColumnCount tabstop,LineCount line)57 ColumnCount column_length(const Buffer& buffer, ColumnCount tabstop, LineCount line)
58 {
59     return get_column(buffer, tabstop, BufferCoord{line, ByteCount{INT_MAX}});
60 }
61 
get_byte_to_column(const Buffer & buffer,ColumnCount tabstop,DisplayCoord coord)62 ByteCount get_byte_to_column(const Buffer& buffer, ColumnCount tabstop, DisplayCoord coord)
63 {
64     auto line = buffer[coord.line];
65     auto col = 0_col;
66     auto it = line.begin();
67     while (it != line.end() and coord.column > col)
68     {
69         if (*it == '\t')
70         {
71             col = (col / tabstop + 1) * tabstop;
72             if (col > coord.column) // the target column was in the tab
73                 break;
74             ++it;
75         }
76         else
77         {
78             auto next = it;
79             col += codepoint_width(utf8::read_codepoint(next, line.end()));
80             if (col > coord.column) // the target column was in the char
81                 break;
82             it = next;
83         }
84     }
85     return (int)(it - line.begin());
86 }
87 
parse_lines(const char * pos,const char * end,EolFormat eolformat)88 static BufferLines parse_lines(const char* pos, const char* end, EolFormat eolformat)
89 {
90     BufferLines lines;
91     while (pos < end)
92     {
93         if (lines.size() >= std::numeric_limits<int>::max())
94             throw runtime_error("too many lines");
95 
96         const char* eol = std::find(pos, end, '\n');
97         if ((eol - pos) >= std::numeric_limits<int>::max())
98             throw runtime_error("line is too long");
99 
100         lines.emplace_back(StringData::create({{pos, eol - (eolformat == EolFormat::Crlf and eol != end ? 1 : 0)}, "\n"}));
101         pos = eol + 1;
102     }
103 
104     if (lines.empty())
105         lines.emplace_back(StringData::create({"\n"}));
106 
107     return lines;
108 }
109 
create_buffer_from_string(String name,Buffer::Flags flags,StringView data)110 Buffer* create_buffer_from_string(String name, Buffer::Flags flags, StringView data)
111 {
112     return BufferManager::instance().create_buffer(
113         std::move(name), flags,
114         parse_lines(data.begin(), data.end(), EolFormat::Lf),
115         ByteOrderMark::None, EolFormat::Lf,
116         FsStatus{InvalidTime, {}, {}});
117 }
118 
119 template<typename Func>
parse_file(StringView filename,Func && func)120 decltype(auto) parse_file(StringView filename, Func&& func)
121 {
122     MappedFile file{parse_filename(filename)};
123 
124     const char* pos = file.data;
125     const char* end = pos + file.st.st_size;
126 
127     auto bom = ByteOrderMark::None;
128     if (file.st.st_size >= 3 && StringView{pos, 3_byte} == "\xEF\xBB\xBF")
129     {
130         bom = ByteOrderMark::Utf8;
131         pos += 3;
132     }
133 
134     bool has_crlf = false, has_lf = false;
135     for (auto it = pos; it != end; ++it)
136     {
137         if (*it == '\n')
138             ((it != pos and *(it-1) == '\r') ? has_crlf : has_lf) = true;
139     }
140     const bool crlf = has_crlf and not has_lf;
141     auto eolformat = crlf ? EolFormat::Crlf : EolFormat::Lf;
142 
143     FsStatus fs_status{file.st.st_mtim, file.st.st_size, hash_data(file.data, file.st.st_size)};
144     return func(parse_lines(pos, end, eolformat), bom, eolformat, fs_status);
145 }
146 
open_file_buffer(StringView filename,Buffer::Flags flags)147 Buffer* open_file_buffer(StringView filename, Buffer::Flags flags)
148 {
149     return parse_file(filename, [&](BufferLines&& lines, ByteOrderMark bom, EolFormat eolformat, FsStatus fs_status)  {
150         return BufferManager::instance().create_buffer(filename.str(), Buffer::Flags::File | flags,
151                                                        std::move(lines), bom, eolformat, fs_status);
152     });
153 }
154 
open_or_create_file_buffer(StringView filename,Buffer::Flags flags)155 Buffer* open_or_create_file_buffer(StringView filename, Buffer::Flags flags)
156 {
157     auto path = parse_filename(filename);
158     if (file_exists(path))
159         return open_file_buffer(filename.str(), Buffer::Flags::File | flags);
160     return create_buffer_from_string(filename.str(), Buffer::Flags::File | Buffer::Flags::New, StringView{});
161 }
162 
reload_file_buffer(Buffer & buffer)163 void reload_file_buffer(Buffer& buffer)
164 {
165     kak_assert(buffer.flags() & Buffer::Flags::File);
166     parse_file(buffer.name(), [&](auto&&... params) {
167         buffer.reload(std::forward<decltype(params)>(params)...);
168     });
169     buffer.flags() &= ~Buffer::Flags::New;
170 }
171 
create_fifo_buffer(String name,int fd,Buffer::Flags flags,bool scroll)172 Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll)
173 {
174     static ValueId fifo_watcher_id = get_free_value_id();
175 
176     auto& buffer_manager = BufferManager::instance();
177     Buffer* buffer = buffer_manager.get_buffer_ifp(name);
178     if (buffer)
179     {
180         buffer->flags() |= Buffer::Flags::NoUndo | flags;
181         buffer->reload({StringData::create({"\n"})}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
182     }
183     else
184         buffer = buffer_manager.create_buffer(
185             std::move(name), flags | Buffer::Flags::Fifo | Buffer::Flags::NoUndo,
186             {StringData::create({"\n"})}, ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
187 
188     struct FifoWatcher : FDWatcher
189     {
190         FifoWatcher(int fd, Buffer& buffer, bool scroll)
191             : FDWatcher(fd, FdEvents::Read, EventMode::Normal,
192                         [](FDWatcher& watcher, FdEvents, EventMode mode) {
193                             if (mode == EventMode::Normal)
194                                 static_cast<FifoWatcher&>(watcher).read_fifo();
195                         }),
196               m_buffer(buffer), m_scroll(scroll)
197         {}
198 
199         ~FifoWatcher()
200         {
201             kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);
202             close_fd();
203             m_buffer.run_hook_in_own_context(Hook::BufCloseFifo, "");
204             m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo);
205         }
206 
207         void read_fifo() const
208         {
209             kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);
210 
211             constexpr size_t buffer_size = 2048;
212             // if we read data slower than it arrives in the fifo, limiting the
213             // iteration number allows us to go back go back to the event loop and
214             // handle other events sources (such as input)
215             constexpr size_t max_loop = 1024;
216             bool closed = false;
217             size_t loop = 0;
218             char data[buffer_size];
219             BufferCoord insert_coord = m_buffer.back_coord();
220             const int fifo = fd();
221 
222             {
223                 auto restore_flags = on_scope_end([this, flags=m_buffer.flags()] { m_buffer.flags() = flags; });
224                 m_buffer.flags() &= ~Buffer::Flags::ReadOnly;
225                 do
226                 {
227 
228                     const ssize_t count = ::read(fifo, data, buffer_size);
229                     if (count <= 0)
230                     {
231                         closed = true;
232                         break;
233                     }
234 
235                     auto pos = m_buffer.back_coord();
236                     const bool prevent_scrolling = pos == BufferCoord{0,0} and not m_scroll;
237                     if (prevent_scrolling)
238                         pos = m_buffer.next(pos);
239 
240                     m_buffer.insert(pos, StringView(data, data+count));
241 
242                     if (prevent_scrolling)
243                     {
244                         m_buffer.erase({0,0}, m_buffer.next({0,0}));
245                         // in the other case, the buffer will have automatically
246                         // inserted a \n to guarantee its invariant.
247                         if (data[count-1] == '\n')
248                             m_buffer.insert(m_buffer.end_coord(), "\n");
249                     }
250                 }
251                 while (++loop < max_loop  and fd_readable(fifo));
252             }
253 
254             if (insert_coord != m_buffer.back_coord())
255                 m_buffer.run_hook_in_own_context(
256                     Hook::BufReadFifo,
257                     selection_to_string(ColumnType::Byte, m_buffer, {insert_coord, m_buffer.back_coord()}));
258 
259             if (closed)
260                 m_buffer.values().erase(fifo_watcher_id); // will delete this
261         }
262 
263         Buffer& m_buffer;
264         bool m_scroll;
265     };
266 
267     buffer->values()[fifo_watcher_id] = Value(std::make_unique<FifoWatcher>(fd, *buffer, scroll));
268     buffer->flags() = flags | Buffer::Flags::Fifo | Buffer::Flags::NoUndo;
269     buffer->run_hook_in_own_context(Hook::BufOpenFifo, buffer->name());
270 
271     return buffer;
272 }
273 
write_to_debug_buffer(StringView str)274 void write_to_debug_buffer(StringView str)
275 {
276     if (not BufferManager::has_instance())
277     {
278         write(2, str);
279         write(2, "\n");
280         return;
281     }
282 
283     constexpr StringView debug_buffer_name = "*debug*";
284     // Try to ensure we keep an empty line at the end of the debug buffer
285     // where the user can put its cursor to scroll with new messages
286     const bool eol_back = not str.empty() and str.back() == '\n';
287     if (Buffer* buffer = BufferManager::instance().get_buffer_ifp(debug_buffer_name))
288     {
289         buffer->flags() &= ~Buffer::Flags::ReadOnly;
290         auto restore = on_scope_end([buffer] { buffer->flags() |= Buffer::Flags::ReadOnly; });
291 
292         buffer->insert(buffer->back_coord(), eol_back ? str : str + "\n");
293     }
294     else
295     {
296         String line = str + (eol_back ? "\n" : "\n\n");
297         create_buffer_from_string(
298             debug_buffer_name.str(), Buffer::Flags::NoUndo | Buffer::Flags::Debug | Buffer::Flags::ReadOnly,
299             line);
300     }
301 }
302 
303 
to_string(Buffer::HistoryId id)304 auto to_string(Buffer::HistoryId id)
305 {
306     using Result = decltype(to_string(size_t{}));
307     if (id == Buffer::HistoryId::Invalid)
308         return Result{1, "-"};
309     return to_string(static_cast<size_t>(id));
310 }
311 
modification_as_string(const Buffer::Modification & modification)312 static String modification_as_string(const Buffer::Modification& modification)
313 {
314     return format("{}{}.{}|{}",
315                   modification.type == Buffer::Modification::Type::Insert ? '+' : '-',
316                   modification.coord.line, modification.coord.column,
317                   modification.content->strview());
318 }
319 
history_as_strings(const Vector<Buffer::HistoryNode> & history)320 Vector<String> history_as_strings(const Vector<Buffer::HistoryNode>& history)
321 {
322     Vector<String> res;
323     for (auto& node : history)
324     {
325         auto seconds = std::chrono::duration_cast<std::chrono::seconds>(node.committed.time_since_epoch());
326         res.push_back(to_string(node.parent));
327         res.push_back(to_string(seconds.count()));
328         res.push_back(to_string(node.redo_child));
329         for (auto& modification : node.undo_group)
330             res.push_back(modification_as_string(modification));
331     };
332     return res;
333 }
334 
undo_group_as_strings(const Buffer::UndoGroup & undo_group)335 Vector<String> undo_group_as_strings(const Buffer::UndoGroup& undo_group)
336 {
337     Vector<String> res;
338     for (auto& modification : undo_group)
339         res.push_back(modification_as_string(modification));
340     return res;
341 }
342 
generate_buffer_name(StringView pattern)343 String generate_buffer_name(StringView pattern)
344 {
345     auto& buffer_manager = BufferManager::instance();
346     for (int i = 0; true; ++i)
347     {
348         String name = format(pattern, i);
349         if (buffer_manager.get_buffer_ifp(name) == nullptr)
350             return name;
351     }
352 }
353 
354 }
355