editor_core 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 368d94707fe992bf22535129bce424ae0dc6a88bc08a2a95fc61e316e3bf1951
4
- data.tar.gz: ea02c89dd36ac8a9c8cc67846930d6bfb16af72a1af6f6ab8cb36e5ab9b55c00
3
+ metadata.gz: ec52d9db82e48638cbc82762edbfc019f825beda5eafa66475f18254c69e1f4e
4
+ data.tar.gz: 354a5ae6fb35b9bb195df0ace5c579a1643b61dbd0e8449ce7428c62b9fefd98
5
5
  SHA512:
6
- metadata.gz: 9b8c159f7083783f53a5dc4ed149ff30998296da4b33a1dc3a86b733294f1a0fdbae1d2b7c6c8bbdf63744b38fe627b2d256117a34dac67b5796bb3d60558cd6
7
- data.tar.gz: 2c66bb04281d89ef2b144b1fa5f08bcaa3add598db1cf833a635d8e32686687d224ee9ab0418fb3ceeaca7598ee1ba3a04625b59fd5b300e5354b3b6101522dd
6
+ metadata.gz: 1d65ef8b4ca8ca3138a67e0684bd3f315569423d715fc6fff022edb2d1c3b717db1efedfb4892711992107056c9afe29e30114beef2fb7c4d3c5350ea3ea8ba1
7
+ data.tar.gz: 483e3d2feb7dda1c78b5634612b92eb3f946c86921ae7898a81a3c73b665fd2d26c1edac0a724faed232b4ee190fcc1c3a1f37f72e979862132c95c400262775
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ *~
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # EditorCore
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/editor_core`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Very basic core concepts for an editor. Used in the next iteration of
4
+ [Re](https://github.com/vidarh/re)
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
6
 
7
7
  ## Installation
8
8
 
@@ -32,7 +32,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
32
 
33
33
  ## Contributing
34
34
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/editor_core.
35
+ Bug reports and pull requests are welcome on GitHub at
36
+ https://github.com/vidarh/editor_core.
36
37
 
37
38
 
38
39
  ## License
@@ -0,0 +1,133 @@
1
+ require 'drb/observer'
2
+ require 'date'
3
+
4
+ require_relative 'history'
5
+
6
+ module EditorCore
7
+ class Buffer
8
+
9
+ include DRb::DRbObservable
10
+
11
+ attr_reader :name, :created_at, :history
12
+ attr_accessor :buffer_id
13
+ attr_writer :created_at
14
+
15
+ def initialize(id,name, lines, created_at = 0)
16
+ @buffer_id = id # An id for this buffer unique for this session
17
+ @name = name
18
+ @history = History.new
19
+ @lines = lines
20
+ @created_at = case created_at
21
+ when Numeric
22
+ Time.at(created_at)
23
+ when Time
24
+ created_at
25
+ when String
26
+ DateTime.parse(created_at)
27
+ else
28
+ raise "Unknown time format: #{created_at}"
29
+ end
30
+ end
31
+
32
+ def as_json(options = { })
33
+ {
34
+ "json_class" => self.class.name,
35
+ "data" => {
36
+ "id" => @buffer_id,
37
+ "name" => @name,
38
+ "lines" => @lines,
39
+ "created_at" => Time.at(created_at.to_i)
40
+ }
41
+ }
42
+ end
43
+
44
+ def to_json(*a)
45
+ as_json.to_json(*a)
46
+ end
47
+
48
+ def self.json_create(o)
49
+ d = o["data"]
50
+ new(d["id"], d["name"], d["lines"], (d["created_at"] || d["modified_at"] || 0))
51
+ end
52
+
53
+ def lines_count
54
+ @lines.size
55
+ end
56
+
57
+ def line_length(row)
58
+ @lines[row]&.size || 0
59
+ end
60
+
61
+ def replace_contents(cursor,new_contents)
62
+ modify(cursor, 0..-1) {|l| new_contents }
63
+ end
64
+
65
+ def insert(cursor, char)
66
+ modify(cursor, cursor.row) {|l| l.insert(cursor.col,char) }
67
+ end
68
+
69
+ def delete(cursor, from, to =nil)
70
+ to ||= from
71
+ modify(cursor, cursor.row) {|l| l[from..to] = ''; l }
72
+ end
73
+
74
+ def break_line(cursor)
75
+ modify(cursor, cursor.row..cursor.row) do |l|
76
+ [l[0][0...cursor.col], l[0][cursor.col..-1]]
77
+ end
78
+ end
79
+
80
+ def join_lines(cursor,offset=0)
81
+ row=cursor.row+offset
82
+ modify(cursor, row..row+1) {|l| l.join }
83
+ end
84
+
85
+ def indent(cursor, row, pos)
86
+ modify(cursor,row) do |line|
87
+ (" "*pos)+line.lstrip
88
+ end
89
+ end
90
+
91
+ def lines(r)
92
+ @lines[r]
93
+ end
94
+
95
+ def can_undo?
96
+ @history.can_undo?
97
+ end
98
+
99
+ def can_redo?
100
+ @history.can_redo?
101
+ end
102
+
103
+ def undo(old_cursor)
104
+ cursor, rowrange, rows = @history.undo_snapshot
105
+ store_snapshot(old_cursor,rowrange, false)
106
+ cursor, rowrange, rows = @history.undo
107
+ @lines[rowrange] = rows
108
+ cursor
109
+ end
110
+
111
+ def redo
112
+ cursor, rowrange, rows = @history.redo
113
+ @lines[rowrange] = rows
114
+ cursor
115
+ end
116
+
117
+ private
118
+
119
+ def store_snapshot(cursor, rowrange, advance = true)
120
+ @history.save([cursor, rowrange, @lines[rowrange]], advance)
121
+ end
122
+
123
+ def modify(cursor, rowrange)
124
+ lines = @lines[rowrange].dup
125
+ lines ||= ""
126
+ new_lines = yield(lines)
127
+ store_snapshot(cursor, rowrange, true)
128
+ @lines[rowrange] = new_lines
129
+ changed
130
+ notify_observers(self)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,189 @@
1
+ #
2
+ # # Core
3
+ #
4
+ # Generic editor functionality that only depends on `Buffer`, `Cursor`
5
+ # and bare minimum of `View`
6
+ #
7
+ # Functionality that goes here should *only* be "objective" behaviours.
8
+ # E.g. what it means to move a cursor to the right is mostly objective.
9
+ # What should happen on enter probably isn't (may or may not include
10
+ # indenting the next line, may or may not involve stripping trailing
11
+ # whitespace, etc.). Methods for the objective, constituent parts of
12
+ # subjective/opinionated operations *can* be added here (e.g. an "enter"
13
+ # method which defers the subjective parts to a block or some hook
14
+ # mechanism could.
15
+ #
16
+ # Knows nothing of configuration or "fancier" options, which should
17
+ # generally be handled in a subclass
18
+ #
19
+ # `@view` must support `#top`/`#top=` and `#height` to control the
20
+ # viewable portion.
21
+ #
22
+ module EditorCore
23
+ class Core
24
+ attr_accessor :cursor, :buffer, :view
25
+
26
+ # ## Basic cursor movement ##
27
+
28
+ def move(row, col); @cursor = cursor.move(buffer, row.to_i, col.to_i) end
29
+ def right(off=1); @cursor = cursor.right(buffer,off.to_i) end
30
+ def left(off=1); @cursor = cursor.left(buffer,off.to_i) end
31
+ def up(off=1); @cursor = cursor.up(buffer,off.to_i) end
32
+ def down(off=1); @cursor = cursor.down(buffer,off.to_i) end
33
+
34
+ def line_home; @cursor = cursor.line_home end
35
+ def line_end; @cursor = cursor.line_end(buffer) end
36
+
37
+ # ## Querying ##
38
+
39
+ def current_line
40
+ @buffer.lines(cursor.row)
41
+ end
42
+
43
+ def get_after
44
+ @buffer.lines(cursor.row)[@cursor.col..-1]
45
+ end
46
+
47
+ ### View-relative cursor movement ##
48
+
49
+ def buffer_home
50
+ view_home
51
+ @cursor = cursor.move(buffer, 0, cursor.col)
52
+ end
53
+
54
+ def buffer_end
55
+ view_end
56
+ @cursor = cursor.move(buffer, buffer.lines_count, cursor.col)
57
+ end
58
+
59
+ def page_down
60
+ lines = view_down(view.height-1)
61
+ @cursor = cursor.down(buffer, lines)
62
+ end
63
+
64
+ def page_up
65
+ @cursor = cursor.up(buffer, view_up(view.height-1))
66
+ end
67
+
68
+ # View movement
69
+ def view_up(offset = 1)
70
+ oldtop = view.top
71
+ view.top -= offset
72
+ view.top = 0 if view.top < 0
73
+ oldtop - view.top
74
+ end
75
+
76
+ def view_down(offset = 1)
77
+ oldtop = view.top
78
+ view.top += offset
79
+ if view.top > buffer.lines_count
80
+ view.top = buffer.lines_count
81
+ end
82
+ view.top - oldtop
83
+ end
84
+
85
+ def view_home
86
+ view.top = 0
87
+ end
88
+
89
+ def view_end
90
+ view.top = buffer.lines_count - view.height
91
+ end
92
+
93
+ # ## Complex navigation ##
94
+ def next_word
95
+ line = current_line
96
+ c = cursor.col
97
+ m = line.length
98
+ while c<m && line[c]&.match(/[^a-zA-Z]/)
99
+ c += 1
100
+ end
101
+ if c >= m
102
+ # FIXME: Pathological cases can cause stack issue here.
103
+ @cursor = Cursor.new(cursor.row,m)
104
+ right
105
+ return next_word
106
+ end
107
+
108
+ if run = line[c..-1]&.match(/([a-zA-Z]+)/)
109
+ c += run[0].length
110
+ #pry([line,c,run])
111
+ end
112
+ off = c - cursor.col
113
+ right(off)
114
+ off
115
+ end
116
+
117
+
118
+ def prev_word
119
+ return if cursor.col == 0
120
+ line = current_line
121
+ c = cursor.col
122
+ if c > 0
123
+ c -= 1
124
+ end
125
+ while c > 0 && line[c] && line[c].match(/[ \t]/)
126
+ c -= 1
127
+ end
128
+ while c > 0 && !(line[c-1].match(/[ \t\-]/))
129
+ c -= 1
130
+ end
131
+ off = cursor.col - c
132
+ @cursor = Cursor.new(cursor.row, c)
133
+ off
134
+ end
135
+
136
+ # ## Mutation ##
137
+
138
+ def delete_before
139
+ buffer.delete(cursor, 0, cursor.col)
140
+ line_home
141
+ end
142
+
143
+ def delete_after
144
+ buffer.delete(cursor, cursor.col,-1)
145
+ end
146
+
147
+ def delete
148
+ return if cursor.end_of_file?(buffer)
149
+
150
+ if cursor.end_of_line?(buffer)
151
+ buffer.join_lines(cursor)
152
+ else
153
+ buffer.delete(cursor, cursor.col)
154
+ end
155
+ end
156
+
157
+ def join_line
158
+ buffer.join_lines(cursor)
159
+ end
160
+
161
+ def backspace
162
+ return if cursor.beginning_of_file?
163
+
164
+ if cursor.col == 0
165
+ cursor_left = buffer.lines(cursor.row).size + 1
166
+ buffer.join_lines(cursor,-1)
167
+ cursor_left.times { left }
168
+ else
169
+ buffer.delete(cursor, cursor.col - 1)
170
+ left
171
+ end
172
+ end
173
+
174
+
175
+ def rstrip_line
176
+ line = current_line
177
+ stripped = current_line.rstrip
178
+ return if line.length == stripped.length
179
+ col = cursor.col
180
+ oldc = cursor
181
+ move(cursor.row, stripped.length)
182
+ delete_after
183
+ if col < stripped.length
184
+ @cursor = oldc
185
+ end
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,72 @@
1
+
2
+ module EditorCore
3
+ class Cursor
4
+ attr_reader :row, :col
5
+
6
+ def initialize(row = 0, col = 0)
7
+ @row = row
8
+ @col = col
9
+ freeze
10
+ end
11
+
12
+ def move(buffer,row,col); self.class.new(row,col).clamp(buffer); end
13
+ def up(buffer, offset = 1); move(buffer, row-offset,col); end
14
+ def down(buffer, offset = 1); move(buffer, row+offset,col); end
15
+
16
+ def left(buffer, offset = 1)
17
+ return Cursor.new(row, col - offset) if offset <= col
18
+ return self if beginning_of_file?
19
+ return move(buffer,row-1,buffer.line_length(row-1))
20
+ end
21
+
22
+ def right(buffer, offset = 1)
23
+ #buffer.right(self,offset)
24
+ return Cursor.new(row, col + offset).clamp(buffer) unless end_of_line?(buffer)
25
+ return self if final_line?(buffer)
26
+ Cursor.new(row + 1, 0)
27
+ end
28
+
29
+ def clamp(buffer)
30
+ row = @row.clamp(0, buffer.lines_count - 1)
31
+ # FIXME: Is this `buffer.lines` check needed? It ought not to be.
32
+ if !buffer.lines(row)
33
+ col = 0
34
+ else
35
+ col = @col.clamp(0, buffer.line_length(row))
36
+ end
37
+ Cursor.new(row,col)
38
+ end
39
+
40
+ def enter(buffer)
41
+ down(buffer).line_home
42
+ end
43
+
44
+ def line_home
45
+ Cursor.new(row, 0)
46
+ end
47
+
48
+ def line_end(buffer)
49
+ Cursor.new(row, buffer.line_length(row))
50
+ end
51
+
52
+ def end_of_line?(buffer)
53
+ if buffer.lines(row)
54
+ col == buffer.line_length(row)
55
+ else
56
+ true
57
+ end
58
+ end
59
+
60
+ def final_line?(buffer)
61
+ row == buffer.lines_count - 1
62
+ end
63
+
64
+ def end_of_file?(buffer)
65
+ final_line?(buffer) && end_of_line?(buffer)
66
+ end
67
+
68
+ def beginning_of_file?
69
+ row == 0 && col == 0
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module EditorCore
3
+ class History
4
+ def initialize
5
+ @snapshots = []
6
+ @current = -1
7
+ end
8
+
9
+ def save(data, advance = true)
10
+ snapshots[@current+1] = data
11
+ @current += 1 if advance
12
+ end
13
+
14
+ def can_undo?
15
+ !undo_snapshot.nil?
16
+ end
17
+
18
+ def undo
19
+ undo_snapshot.tap { @current -= 1 }
20
+ end
21
+
22
+ def can_redo?
23
+ !redo_snapshot.nil?
24
+ end
25
+
26
+ def redo
27
+ redo_snapshot.tap { @current += 1 }
28
+ end
29
+
30
+ def undo_snapshot
31
+ snapshots[current] if current >= 0
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :snapshots, :current
37
+
38
+ def redo_snapshot
39
+ snapshots[current + 2]
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module EditorCore
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: editor_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-25 00:00:00.000000000 Z
11
+ date: 2021-11-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -28,6 +28,10 @@ files:
28
28
  - bin/setup
29
29
  - editor_core.gemspec
30
30
  - lib/editor_core.rb
31
+ - lib/editor_core/buffer.rb
32
+ - lib/editor_core/core.rb
33
+ - lib/editor_core/cursor.rb
34
+ - lib/editor_core/history.rb
31
35
  - lib/editor_core/version.rb
32
36
  homepage: https://github.com/vidarh/editor_core
33
37
  licenses: