Skip to content

Commit 94ac1ac

Browse files
committed
[1.1.0] Added table with installed plugins
1 parent a896006 commit 94ac1ac

3 files changed

Lines changed: 207 additions & 57 deletions

File tree

jb_updater/src/gui/main_gui.cr

Lines changed: 182 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ module App
3838
@@search_id : Int64 = 0
3939
@@detected_products : Array(JBUpdater::DetectedProduct)? = nil
4040
@@installed_plugins : Hash(String, JBUpdater::PluginMeta)? = nil
41+
@@installed_plugins_arr : Array(JBUpdater::PluginMeta) = [] of JBUpdater::PluginMeta
42+
@@installed_table : UIng::Table? = nil
43+
@@installed_model : UIng::Table::Model? = nil
4144

4245
# Background operation state
4346
@@op_status : String = ""
@@ -162,6 +165,35 @@ module App
162165

163166
def self.installed_plugins=(plugins : Hash(String, JBUpdater::PluginMeta)?)
164167
@@installed_plugins = plugins
168+
refresh_installed_list
169+
end
170+
171+
def self.installed_plugins_arr
172+
@@installed_plugins_arr
173+
end
174+
175+
def self.refresh_installed_list
176+
if hash = @@installed_plugins
177+
@@installed_plugins_arr = hash.values.sort_by!(&.id)
178+
else
179+
@@installed_plugins_arr = [] of JBUpdater::PluginMeta
180+
end
181+
end
182+
183+
def self.installed_table
184+
@@installed_table
185+
end
186+
187+
def self.installed_table=(table : UIng::Table?)
188+
@@installed_table = table
189+
end
190+
191+
def self.installed_model
192+
@@installed_model
193+
end
194+
195+
def self.installed_model=(model : UIng::Table::Model?)
196+
@@installed_model = model
165197
end
166198

167199
def self.browse_detail
@@ -866,6 +898,117 @@ UIng.init do
866898

867899
tabs.append("Browse", browse_tab)
868900

901+
# --- Installed tab ---------------------------------------------------
902+
installed_tab = UIng::Box.new(:vertical)
903+
installed_tab.padded = true
904+
905+
installed_handler = UIng::Table::Model::Handler.new do
906+
num_columns { 5 }
907+
column_type { |_col| UIng::Table::Value::Type::String }
908+
num_rows { App.installed_plugins_arr.size }
909+
cell_value { |row, col|
910+
plugin = App.installed_plugins_arr[row]?
911+
next UIng::Table::Value.new("") unless plugin
912+
case col
913+
when 0 then UIng::Table::Value.new(plugin.name || plugin.id)
914+
when 1 then UIng::Table::Value.new(plugin.id)
915+
when 2 then UIng::Table::Value.new(plugin.version)
916+
when 3 then UIng::Table::Value.new(plugin.since || "")
917+
else UIng::Table::Value.new(plugin.until_build || "")
918+
end
919+
}
920+
end
921+
installed_model = UIng::Table::Model.new(installed_handler)
922+
App.installed_table = installed_table = UIng::Table.new(installed_model)
923+
App.installed_model = installed_model
924+
installed_table.header_visible = true
925+
installed_table.append_text_column("Name", 0, -1)
926+
installed_table.append_text_column("Plugin ID", 1, -1)
927+
installed_table.append_text_column("Version", 2, -1)
928+
installed_table.append_text_column("Since Build", 3, -1)
929+
installed_table.append_text_column("Until Build", 4, -1)
930+
installed_table.selection_mode = :one
931+
932+
installed_actions = UIng::Box.new(:horizontal)
933+
installed_actions.padded = true
934+
935+
btn_scan_installed = UIng::Button.new("Scan")
936+
btn_uninstall = UIng::Button.new("Uninstall selected")
937+
btn_uninstall.disable
938+
939+
installed_actions.append(btn_scan_installed, false)
940+
installed_actions.append(btn_uninstall, false)
941+
942+
installed_status = UIng::Label.new("Click Scan to list installed plugins")
943+
944+
installed_tab.append(installed_actions, false)
945+
installed_tab.append(installed_table, true)
946+
installed_tab.append(installed_status, false)
947+
948+
tabs.append("Installed", installed_tab)
949+
950+
btn_scan_installed.on_clicked do
951+
dir = expand_tilde(e_plugins_dir.text) || e_plugins_dir.text || ""
952+
if dir.empty?
953+
installed_status.text = "Set Plugins Directory first"
954+
next
955+
end
956+
scanned = JBUpdater::PluginMeta.scan_dir(dir) rescue nil
957+
if scanned
958+
old_count = App.installed_plugins_arr.size
959+
App.installed_plugins = scanned
960+
if old_count == 0
961+
App.installed_plugins_arr.each_with_index { |_, i| App.installed_model.try &.row_inserted(i) }
962+
else
963+
(0...[App.installed_plugins_arr.size, old_count].min).each { |i| App.installed_model.try &.row_changed(i) }
964+
if App.installed_plugins_arr.size > old_count
965+
(old_count...App.installed_plugins_arr.size).each { |i| App.installed_model.try &.row_inserted(i) }
966+
elsif App.installed_plugins_arr.size < old_count
967+
(App.installed_plugins_arr.size...old_count).reverse_each { |i| App.installed_model.try &.row_deleted(i) }
968+
end
969+
end
970+
installed_status.text = "Found #{scanned.size} installed plugins"
971+
else
972+
installed_status.text = "Error scanning plugins directory"
973+
end
974+
end
975+
976+
installed_table.on_selection_changed do |selection|
977+
if selection.num_rows > 0
978+
btn_uninstall.enable
979+
else
980+
btn_uninstall.disable
981+
end
982+
end
983+
984+
btn_uninstall.on_clicked do
985+
UIng.queue_main do
986+
installed_table.selection do |sel|
987+
next if sel.num_rows == 0
988+
row = sel.rows[0]
989+
plugin = App.installed_plugins_arr[row]?
990+
if plugin
991+
FileUtils.rm_rf(plugin.path)
992+
scanned = JBUpdater::PluginMeta.scan_dir(File.dirname(plugin.path)) rescue nil
993+
old_count = App.installed_plugins_arr.size
994+
App.installed_plugins = scanned
995+
if old_count == 0
996+
App.installed_plugins_arr.each_with_index { |_, i| App.installed_model.try &.row_inserted(i) }
997+
else
998+
(0...[App.installed_plugins_arr.size, old_count].min).each { |i| App.installed_model.try &.row_changed(i) }
999+
if App.installed_plugins_arr.size > old_count
1000+
(old_count...App.installed_plugins_arr.size).each { |i| App.installed_model.try &.row_inserted(i) }
1001+
elsif App.installed_plugins_arr.size < old_count
1002+
(App.installed_plugins_arr.size...old_count).reverse_each { |i| App.installed_model.try &.row_deleted(i) }
1003+
end
1004+
end
1005+
installed_status.text = "Deleted: #{plugin.id}"
1006+
btn_uninstall.disable
1007+
end
1008+
end
1009+
end
1010+
end
1011+
8691012
# --- IDE tab --------------------------------------------------------
8701013
ide_tab = UIng::Box.new(:vertical)
8711014
ide_tab.padded = true
@@ -1092,6 +1235,12 @@ UIng.init do
10921235
}
10931236
load_installed_for_browse.call
10941237

1238+
# Populate installed tab model with startup data
1239+
inst = App.installed_plugins
1240+
if inst && inst.size > 0
1241+
App.installed_plugins_arr.each_with_index { |_, i| App.installed_model.try &.row_inserted(i) }
1242+
end
1243+
10951244
# Warm marketplace cache after UI is visible (1s delay)
10961245
UIng.timer(1_000) {
10971246
build = resolve_build.call
@@ -1100,44 +1249,42 @@ UIng.init do
11001249
0
11011250
}
11021251

1103-
search_entry.on_changed do |_text|
1104-
UIng.queue_main do
1105-
begin
1106-
query = search_entry.text || ""
1107-
if query.empty?
1108-
model = App.browse_table_model
1109-
if model
1110-
old_count = App.browse_plugins.size
1111-
(0...old_count).each { |i| model.row_deleted(i) }
1112-
end
1252+
search_entry.on_changed do |text|
1253+
begin
1254+
query = text || ""
1255+
if query.empty?
1256+
model = App.browse_table_model
1257+
if model
1258+
old_count = App.browse_plugins.size
11131259
App.browse_plugins = [] of JBUpdater::PluginInfo
1114-
App.selected_xml_id = nil
1115-
browse_status.text = "Type to search plugins..."
1116-
next
1260+
(0...old_count).each { |i| model.row_deleted(0) }
11171261
end
1262+
App.selected_xml_id = nil
1263+
browse_status.text = "Type to search plugins..."
1264+
next
1265+
end
11181266

1119-
build = resolve_build.call
1120-
plugins = JBUpdater::PluginMarketplace.search(query, build)
1121-
1122-
model = App.browse_table_model
1123-
next unless model
1267+
build = resolve_build.call
1268+
plugins = JBUpdater::PluginMarketplace.search(query, build)
11241269

1125-
old_count = App.browse_plugins.size
1126-
App.browse_plugins = plugins
1127-
if old_count == 0
1128-
plugins.each_with_index { |_, i| model.row_inserted(i) }
1129-
elsif plugins.size >= old_count
1130-
(0...old_count).each { |i| model.row_changed(i) }
1131-
(old_count...plugins.size).each { |i| model.row_inserted(i) }
1132-
else
1133-
(0...plugins.size).each { |i| model.row_changed(i) }
1134-
(plugins.size...old_count).each { |i| model.row_deleted(i) }
1135-
end
1136-
browse_status.text = "Found #{plugins.size} results"
1137-
rescue ex
1138-
log.append("[Browse] Search error: #{ex.class}: #{ex.message}\n")
1139-
browse_status.text = "Search error: #{ex.class} #{ex.message}"
1270+
model = App.browse_table_model
1271+
next unless model
1272+
1273+
old_count = App.browse_plugins.size
1274+
App.browse_plugins = plugins
1275+
if old_count == 0
1276+
plugins.each_with_index { |_, i| model.row_inserted(i) }
1277+
elsif plugins.size >= old_count
1278+
(0...old_count).each { |i| model.row_changed(i) }
1279+
(old_count...plugins.size).each { |i| model.row_inserted(i) }
1280+
else
1281+
(0...plugins.size).each { |i| model.row_changed(i) }
1282+
(plugins.size...old_count).reverse_each { |i| model.row_deleted(i) }
11401283
end
1284+
browse_status.text = "Found #{plugins.size} results"
1285+
rescue ex
1286+
log.append("[Browse] Search error: #{ex.class}: #{ex.message}\n")
1287+
browse_status.text = "Search error: #{ex.class} #{ex.message}"
11411288
end
11421289
end
11431290

@@ -1161,7 +1308,7 @@ UIng.init do
11611308
(old_count...plugins.size).each { |i| model.row_inserted(i) }
11621309
else
11631310
(0...plugins.size).each { |i| model.row_changed(i) }
1164-
(plugins.size...old_count).each { |i| model.row_deleted(i) }
1311+
(plugins.size...old_count).reverse_each { |i| model.row_deleted(i) }
11651312
end
11661313
browse_status.text = "Loaded #{plugins.size} plugins (top downloads)"
11671314
end
@@ -1211,7 +1358,7 @@ UIng.init do
12111358
plugin = row >= 0 ? App.browse_plugins[row]? : nil
12121359
if plugin
12131360
App.selected_xml_id = plugin.xml_id
1214-
stripped = JBUpdater::PluginMarketplace.html_strip(plugin.description)
1361+
stripped = plugin.description[0, 500]
12151362
preview = stripped[0, 500]
12161363
App.log.append("[Browse] detail: #{preview.size}B #{preview.count('\n')} lines (#{preview.size - preview.count('\n')} non-newline)\n")
12171364
App.safe_set_text(browse_detail, preview)

jb_updater/src/jb_updater/plugin_marketplace.cr

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ require "http/client"
33
require "xml"
44

55
module JBUpdater
6+
# Strips HTML tags and decodes entities from a string.
7+
def self.html_strip(html : String) : String
8+
text = html.gsub(/<[^>]*>/, " ")
9+
.gsub("&amp;", "&")
10+
.gsub("&lt;", "<")
11+
.gsub("&gt;", ">")
12+
.gsub("&quot;", "\"")
13+
.gsub("&#39;", "'")
14+
.gsub(/&#(\d+);/) { $1.to_i.chr rescue "?" }
15+
text.gsub(/\s+/, " ").strip
16+
end
17+
618
# Metadata for a plugin listed on the JetBrains Marketplace.
719
struct PluginInfo
820
# Numeric ID on the marketplace.
@@ -110,7 +122,7 @@ module JBUpdater
110122
id: 0_i64,
111123
xml_id: xml_id,
112124
name: name,
113-
description: description.strip.gsub(/\s+/, " "),
125+
description: JBUpdater.html_strip(description).gsub(/\s+/, " ").strip,
114126
categories: categories,
115127
downloads: downloads,
116128
vendor: vendor,
@@ -200,18 +212,6 @@ module JBUpdater
200212
plugins
201213
end
202214

203-
# Strips HTML tags and decodes entities from a string.
204-
def self.html_strip(html : String) : String
205-
text = html.gsub(/<[^>]*>/, " ")
206-
.gsub("&amp;", "&")
207-
.gsub("&lt;", "<")
208-
.gsub("&gt;", ">")
209-
.gsub("&quot;", "\"")
210-
.gsub("&#39;", "'")
211-
.gsub(/&#(\d+);/) { $1.to_i.chr rescue "?" }
212-
text.gsub(/\s+/, " ").strip
213-
end
214-
215215
# Returns the first cached build key, or `nil`.
216216
#
217217
# @return [String?]

jb_updater/src/jb_updater/plugin_meta.cr

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module JBUpdater
1010
class PluginMeta
1111
# Plugin XML ID (e.g. `"com.intellij.database"`).
1212
getter id : String
13+
# Plugin display name (e.g. `"Database Tools and SQL"`).
14+
getter name : String?
1315
# Plugin version string (e.g. `"2025.1.0"`).
1416
getter version : String
1517
# Minimum compatible IDE build (e.g. `"252.0"`) or `nil`.
@@ -27,12 +29,14 @@ module JBUpdater
2729
def initialize(
2830
*,
2931
id : String,
32+
name : String? = nil,
3033
version : String,
3134
since : String? = nil,
3235
until_build : String? = nil,
3336
path : String,
3437
)
3538
@id = id
39+
@name = name
3640
@version = version
3741
@since = since
3842
@until_build = until_build
@@ -93,27 +97,26 @@ module JBUpdater
9397
def self.parse_xml(xml : String, path : String) : PluginMeta?
9498
doc = XML.parse(xml)
9599
id_node = doc.xpath_node("//id") || doc.xpath_node("//name")
100+
name_node = doc.xpath_node("//name")
96101
version_node = doc.xpath_node("//version")
97102
idea_version = doc.xpath_node("//idea-version")
98103
since = idea_version.try &.["since-build"]? || idea_version.try &.["sinceBuild"]?
99104
until_build = idea_version.try &.["until-build"]? || idea_version.try &.["untilBuild"]?
100105
id = id_node.try &.content
106+
name = name_node.try &.content
101107
version = version_node.try &.content
102108
return nil unless id && version
103-
new(id: id.strip, version: version.strip, since: since.try &.strip, until_build: until_build.try &.strip, path: path)
109+
new(id: id.strip, name: name.try(&.strip), version: version.strip, since: since.try &.strip, until_build: until_build.try &.strip, path: path)
104110
rescue ex : XML::Error
105111
nil
106112
end
107113

108-
# Reads a file from inside a JAR using the `unzip -p` command.
109-
#
110-
# @param jar_path [String] Path to the JAR file
111-
# @param inner_path [String] Path inside the JAR (e.g. `"META-INF/plugin.xml"`)
112-
# @return [String?] File content or `nil` if not found
114+
# Reads a file from inside a JAR using `unzip -p`.
113115
def self.read_text_from_jar(jar_path : String, inner_path : String) : String?
114-
out, status = Utils.run_cmd("unzip", "-p", jar_path, inner_path)
115-
status.success? ? out : nil
116-
rescue ex
116+
io = IO::Memory.new
117+
status = Process.run("unzip", {"-p", jar_path, inner_path}, output: io, error: :close)
118+
status.success? ? io.to_s : nil
119+
rescue
117120
nil
118121
end
119122
end

0 commit comments

Comments
 (0)