[Feat] support highlight code block

pull/100/head
Mist 2024-04-27 19:07:41 +08:00 committed by The Mist
parent 00e2156364
commit 9836ad1163
18 changed files with 228 additions and 17 deletions

View File

@ -3,6 +3,7 @@ pub mod breadcrumbs;
pub mod code_block; pub mod code_block;
pub mod container; pub mod container;
pub mod editor; pub mod editor;
pub mod highlight_code_block;
pub mod interface; pub mod interface;
pub mod line_number; pub mod line_number;
pub mod rect; pub mod rect;

View File

@ -35,6 +35,7 @@ impl Component for Background {
context: &ComponentContext, context: &ComponentContext,
_render_params: &RenderParams, _render_params: &RenderParams,
_style: &ComponentStyle, _style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
let mut paint = Paint::default(); let mut paint = Paint::default();
let w = pixmap.width() as f32; let w = pixmap.width() as f32;

View File

@ -5,7 +5,7 @@ use crate::{code::calc_wh, edges::margin::Margin, text::FontRenderer};
use super::interface::{ use super::interface::{
component::Component, component::Component,
style::{RawComponentStyle, Size}, style::{ComponentStyle, RawComponentStyle, Size},
}; };
pub struct Breadcrumbs { pub struct Breadcrumbs {
@ -41,6 +41,7 @@ impl Component for Breadcrumbs {
context: &super::interface::component::ComponentContext, context: &super::interface::component::ComponentContext,
render_params: &super::interface::component::RenderParams, render_params: &super::interface::component::RenderParams,
style: &super::interface::style::ComponentStyle, style: &super::interface::style::ComponentStyle,
_parent_style: &ComponentStyle,
) -> super::interface::render_error::Result<()> { ) -> super::interface::render_error::Result<()> {
if self.has_breadcrumbs { if self.has_breadcrumbs {
let attrs = Attrs::new() let attrs = Attrs::new()

View File

@ -33,6 +33,7 @@ impl Component for Code {
context: &ComponentContext, context: &ComponentContext,
render_params: &RenderParams, render_params: &RenderParams,
style: &ComponentStyle, style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
let params = &context.take_snapshot_params; let params = &context.take_snapshot_params;
let highlight = Highlight::new( let highlight = Highlight::new(

View File

@ -33,6 +33,7 @@ impl Component for MacTitleBar {
context: &ComponentContext, context: &ComponentContext,
render_params: &RenderParams, render_params: &RenderParams,
_style: &ComponentStyle, _style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
self.draw_control_buttons( self.draw_control_buttons(
// Control bar construct by draw circles, after drawn, the path will be at the center, // Control bar construct by draw circles, after drawn, the path will be at the center,

View File

@ -0,0 +1,76 @@
use super::{
interface::{component::Component, style::ComponentStyle},
rect::EDITOR_PADDING,
};
use tiny_skia::{Color, Paint, Rect, Transform};
#[derive(Default)]
pub struct HighlightCodeBlock {
children: Vec<Box<dyn Component>>,
line_height: f32,
start_line_number: usize,
end_line_number: usize,
render_condition: bool,
}
impl Component for HighlightCodeBlock {
fn children(&self) -> &Vec<Box<dyn Component>> {
&self.children
}
fn render_condition(&self) -> bool {
self.render_condition
}
fn draw_self(
&self,
pixmap: &mut tiny_skia::Pixmap,
context: &super::interface::component::ComponentContext,
render_params: &super::interface::component::RenderParams,
_style: &super::interface::style::ComponentStyle,
parent_style: &ComponentStyle,
) -> super::interface::render_error::Result<()> {
let mut paint = Paint::default();
let start_y_offset = (self.start_line_number - 1) as f32 * self.line_height;
paint.anti_alias = false;
paint.set_color(Color::from_rgba8(255, 255, 255, 18));
pixmap.fill_rect(
Rect::from_xywh(
render_params.x - EDITOR_PADDING,
render_params.y + start_y_offset,
parent_style.width + EDITOR_PADDING * 2.,
(self.end_line_number - self.start_line_number + 1) as f32 * self.line_height,
)
.unwrap(),
&paint,
Transform::from_scale(context.scale_factor, context.scale_factor),
None,
);
Ok(())
}
}
impl HighlightCodeBlock {
pub fn from_line_number(
start_line_number: Option<usize>,
end_line_number: Option<usize>,
line_height: f32,
) -> HighlightCodeBlock {
if end_line_number < start_line_number {
panic!("end_line_number should be greater than start_line_number")
}
match start_line_number {
Some(start_line_number) => HighlightCodeBlock {
render_condition: true,
children: vec![],
line_height,
start_line_number,
end_line_number: end_line_number.unwrap(),
},
None => HighlightCodeBlock::default(),
}
}
}

View File

@ -70,6 +70,7 @@ pub trait Component {
_context: &ComponentContext, _context: &ComponentContext,
_render_params: &RenderParams, _render_params: &RenderParams,
_style: &ComponentStyle, _style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
Ok(()) Ok(())
} }
@ -113,13 +114,13 @@ pub trait Component {
let style = self.parsed_style(); let style = self.parsed_style();
let render_params = self.initialize( let render_params = self.initialize(
&component_render_params.parse_into_render_params_with_style( &component_render_params.parse_into_render_params_with_style(
parent_style, parent_style.clone(),
sibling_style, sibling_style,
style.clone(), style.clone(),
), ),
); );
self.draw_self(pixmap, context, &render_params, &style)?; self.draw_self(pixmap, context, &render_params, &style, &parent_style)?;
let children = self.children(); let children = self.children();
let mut sibling_render_params = RenderParams { let mut sibling_render_params = RenderParams {

View File

@ -44,6 +44,7 @@ impl Component for LineNumber {
context: &ComponentContext, context: &ComponentContext,
render_params: &RenderParams, render_params: &RenderParams,
style: &ComponentStyle, style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
FontRenderer::new( FontRenderer::new(
FONT_SIZE, FONT_SIZE,

View File

@ -1,12 +1,13 @@
use crate::edges::padding::Padding;
use super::interface::{ use super::interface::{
component::{Component, ComponentContext, RenderParams}, component::{Component, ComponentContext, RenderParams},
render_error, render_error,
style::{ComponentAlign, ComponentStyle, RawComponentStyle, Style}, style::{ComponentAlign, ComponentStyle, RawComponentStyle, Style},
}; };
use crate::edges::padding::Padding;
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Transform}; use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Transform};
pub const EDITOR_PADDING: f32 = 20.;
pub struct Rect { pub struct Rect {
radius: f32, radius: f32,
children: Vec<Box<dyn Component>>, children: Vec<Box<dyn Component>>,
@ -20,7 +21,7 @@ impl Component for Rect {
fn style(&self) -> RawComponentStyle { fn style(&self) -> RawComponentStyle {
Style::default() Style::default()
.align(ComponentAlign::Column) .align(ComponentAlign::Column)
.padding(Padding::from_value(20.)) .padding(Padding::from_value(EDITOR_PADDING))
} }
fn draw_self( fn draw_self(
@ -29,6 +30,7 @@ impl Component for Rect {
context: &ComponentContext, context: &ComponentContext,
render_params: &RenderParams, render_params: &RenderParams,
style: &ComponentStyle, style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
let mut path_builder = PathBuilder::new(); let mut path_builder = PathBuilder::new();
let x = render_params.x; let x = render_params.x;

View File

@ -21,6 +21,7 @@ impl Component for Watermark {
context: &ComponentContext, context: &ComponentContext,
render_params: &RenderParams, render_params: &RenderParams,
_style: &ComponentStyle, _style: &ComponentStyle,
_parent_style: &ComponentStyle,
) -> render_error::Result<()> { ) -> render_error::Result<()> {
let params = &context.take_snapshot_params; let params = &context.take_snapshot_params;

View File

@ -27,6 +27,8 @@ pub struct TakeSnapshotParams {
pub breadcrumbs_separator: String, pub breadcrumbs_separator: String,
pub has_breadcrumbs: bool, pub has_breadcrumbs: bool,
pub start_line_number: Option<usize>, pub start_line_number: Option<usize>,
pub highlight_start_line_number: Option<usize>,
pub highlight_end_line_number: Option<usize>,
} }
impl FromObject for TakeSnapshotParams { impl FromObject for TakeSnapshotParams {

View File

@ -8,6 +8,7 @@ use crate::components::code_block::CodeBlock;
use crate::components::container::Container; use crate::components::container::Container;
use crate::components::editor::code::Code; use crate::components::editor::code::Code;
use crate::components::editor::mac_title_bar::MacTitleBar; use crate::components::editor::mac_title_bar::MacTitleBar;
use crate::components::highlight_code_block::HighlightCodeBlock;
use crate::components::interface::component::ComponentContext; use crate::components::interface::component::ComponentContext;
use crate::components::interface::render_error; use crate::components::interface::render_error;
use crate::components::line_number::LineNumber; use crate::components::line_number::LineNumber;
@ -17,6 +18,7 @@ use crate::config::TakeSnapshotParams;
// Scale the screenshot to 3 times its size // Scale the screenshot to 3 times its size
const SCALE_FACTOR: f32 = 3.; const SCALE_FACTOR: f32 = 3.;
const LINE_HEIGHT: f32 = 20.;
// The params is come from neovim instance // The params is come from neovim instance
pub fn take_snapshot(params: TakeSnapshotParams) -> render_error::Result<Pixmap> { pub fn take_snapshot(params: TakeSnapshotParams) -> render_error::Result<Pixmap> {
@ -36,8 +38,17 @@ pub fn take_snapshot(params: TakeSnapshotParams) -> render_error::Result<Pixmap>
params.has_breadcrumbs, params.has_breadcrumbs,
)), )),
Box::new(CodeBlock::from_children(vec![ Box::new(CodeBlock::from_children(vec![
Box::new(LineNumber::new(&params.code, params.start_line_number, 20.)), Box::new(HighlightCodeBlock::from_line_number(
Box::new(Code::new(params.code, 20., 15.)), params.highlight_start_line_number,
params.highlight_end_line_number,
LINE_HEIGHT,
)),
Box::new(LineNumber::new(
&params.code,
params.start_line_number,
LINE_HEIGHT,
)),
Box::new(Code::new(params.code, LINE_HEIGHT, 15.)),
])), ])),
], ],
)), )),

View File

@ -0,0 +1,54 @@
local string_utils = require("codesnap.utils.string")
local table_utils = require("codesnap.utils.table")
local highlight_module = {}
function highlight_module.call_cb_with_parsed_config(cb_name, highlight_start_line_number, highlight_end_line_number)
vim.api.nvim_buf_delete(0, {})
vim.schedule(function()
local main = require("codesnap")
local config = table_utils.merge(main.highlight_mode_config, {
highlight_start_line_number = highlight_start_line_number,
highlight_end_line_number = highlight_end_line_number,
})
main[cb_name](config)
end)
end
function highlight_module.create_highlight_selector_window(cb_name, code)
local width = 100
local height = 20
local row = vim.fn.winheight(0) / 2 - height / 2
local col = vim.fn.winwidth(0) / 2 - width / 2
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, string_utils.split(code, "\n"))
local window_id = vim.api.nvim_open_win(bufnr, false, {
relative = "editor",
width = width,
height = height,
col = col,
row = row,
style = "minimal",
border = "rounded",
title = "Select the code needs to be highlighted",
title_pos = "center",
})
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
vim.api.nvim_buf_set_option(bufnr, "filetype", vim.bo.filetype)
vim.api.nvim_buf_set_keymap(bufnr, "n", "q", ":q<CR>", {})
vim.api.nvim_buf_set_keymap(bufnr, "", "<ESC>", ":q<CR>", {})
vim.api.nvim_buf_set_keymap(
bufnr,
"v",
"<CR>",
":lua require('codesnap.highlight').call_cb_with_parsed_config('"
.. cb_name
.. "', require('codesnap.utils.visual').get_start_line_number(), require('codesnap.utils.visual').get_end_line_number())<CR>",
{ silent = true }
)
vim.api.nvim_set_current_win(window_id)
end
return highlight_module

View File

@ -2,23 +2,25 @@ local static = require("codesnap.static")
local table_utils = require("codesnap.utils.table") local table_utils = require("codesnap.utils.table")
local string_utils = require("codesnap.utils.string") local string_utils = require("codesnap.utils.string")
local config_module = require("codesnap.config") local config_module = require("codesnap.config")
local highlight_module = require("codesnap.highlight")
local main = { local main = {
cwd = static.cwd, cwd = static.cwd,
preview_switch = static.preview_switch, preview_switch = static.preview_switch,
highlight_mode_config = nil,
} }
function main.setup(config) function main.setup(config)
static.config = table_utils.merge(static.config, config == nil and {} or config) static.config = table_utils.merge(static.config, config == nil and {} or config)
end end
function main.copy_into_clipboard(extension) function main.copy_into_clipboard_with_config(config)
require("generator").copy_into_clipboard(config_module.get_config(extension)) require("generator").copy_into_clipboard(config)
vim.cmd("delmarks <>") vim.cmd("delmarks <>")
vim.notify("Save snapshot into clipboard successfully") vim.notify("Save snapshot into clipboard successfully")
end end
function main.save_snapshot(extension) function main.save_snapshot_with_config(config)
if string_utils.is_str_empty(static.config.save_path) then if string_utils.is_str_empty(static.config.save_path) then
error( error(
"If you want to save snapshot in somewhere, you should config the save_path before, refer: https://github.com/mistricky/codesnap.nvim?tab=readme-ov-file#save-the-snapshot", "If you want to save snapshot in somewhere, you should config the save_path before, refer: https://github.com/mistricky/codesnap.nvim?tab=readme-ov-file#save-the-snapshot",
@ -32,12 +34,32 @@ function main.save_snapshot(extension)
error("The extension of save_path should be .png", 0) error("The extension of save_path should be .png", 0)
end end
local config = config_module.get_config(extension)
require("generator").save_snapshot(config) require("generator").save_snapshot(config)
vim.cmd("delmarks <>") vim.cmd("delmarks <>")
---@diagnostic disable-next-line: need-check-nil ---@diagnostic disable-next-line: need-check-nil
vim.notify("Save snapshot in " .. config.save_path .. " successfully") vim.notify("Save snapshot in " .. config.save_path .. " successfully")
end end
-- Take a snapshot and copy it into clipboard
function main.copy_into_clipboard(extension)
main.copy_into_clipboard_with_config(config_module.get_config(extension))
end
-- Take a snapshot and save it into the specified path
function main.save_snapshot(extension)
main.save_snapshot_with_config(config_module.get_config(extension))
end
function main.highlight_mode_copy_into_clipboard(extension)
main.highlight_mode_config = config_module.get_config(extension)
highlight_module.create_highlight_selector_window("copy_into_clipboard_with_config", main.highlight_mode_config.code)
end
function main.highlight_mode_save_snapshot(extension)
main.highlight_mode_config = config_module.get_config(extension)
highlight_module.create_highlight_selector_window("save_snapshot_with_config", main.highlight_mode_config.code)
end
return main return main

View File

@ -24,4 +24,14 @@ function string_util.convert_empty_to_nil(target)
end end
end end
function string_util.split(str, delimiter)
local result = {}
for token in string.gmatch(str, "[^" .. delimiter .. "]+") do
table.insert(result, token)
end
return result
end
return string_util return string_util

View File

@ -33,7 +33,7 @@ end
function table_utils.serialize_array(t) function table_utils.serialize_array(t)
local result = list_utils.map(t, function(ele) local result = list_utils.map(t, function(ele)
table_utils.serialize_json(ele) table_utils.to_string(ele)
end) end)
return "[" .. result.concat(t, ",") .. "]" return "[" .. result.concat(t, ",") .. "]"
@ -43,17 +43,17 @@ function table_utils.serialize_table(t)
local result = {} local result = {}
for key, value in pairs(t) do for key, value in pairs(t) do
table.insert(result, string.format('"%s":%s', key, table_utils.serialize_json(value))) table.insert(result, string.format("%s = %s", key, table_utils.to_string(value)))
end end
return "{" .. table.concat(result, ",") .. "}" return "{" .. table.concat(result, ",") .. "}"
end end
function table_utils.serialize_string(value) function table_utils.serialize_string(value)
return '"' .. value .. '"' return "[[" .. value .. "]]"
end end
function table_utils.serialize_json(t) function table_utils.to_string(t)
local complex_type_parser = { local complex_type_parser = {
array = table_utils.serialize_array, array = table_utils.serialize_array,
table = table_utils.serialize_table, table = table_utils.serialize_table,
@ -67,4 +67,14 @@ function table_utils.serialize_json(t)
return parse(t) return parse(t)
end end
-- function table_utils.to_string(t)
-- local result = ""
--
-- for key, value in pairs(t) do
-- result = result .. key .. ":" .. tostring(value) .. ","
-- end
--
-- return "{" .. result .. "}"
-- end
return table_utils return table_utils

View File

@ -18,6 +18,10 @@ function visual_utils.get_start_line_number()
return vim.fn.line("'<") return vim.fn.line("'<")
end end
function visual_utils.get_end_line_number()
return vim.fn.line("'>")
end
function visual_utils.get_selected_text() function visual_utils.get_selected_text()
local selected_text = vim.fn.getline("'<", "'>") local selected_text = vim.fn.getline("'<", "'>")

View File

@ -22,3 +22,15 @@ end
vim.api.nvim_create_user_command("CodeSnap", take_snapshot(codesnap.copy_into_clipboard), { nargs = "*", range = "%" }) vim.api.nvim_create_user_command("CodeSnap", take_snapshot(codesnap.copy_into_clipboard), { nargs = "*", range = "%" })
vim.api.nvim_create_user_command("CodeSnapSave", take_snapshot(codesnap.save_snapshot), { nargs = "*", range = "%" }) vim.api.nvim_create_user_command("CodeSnapSave", take_snapshot(codesnap.save_snapshot), { nargs = "*", range = "%" })
vim.api.nvim_create_user_command(
"CodeSnapHighlight",
take_snapshot(codesnap.highlight_mode_copy_into_clipboard),
{ nargs = "*", range = "%" }
)
vim.api.nvim_create_user_command(
"CodeSnapSaveHighlight",
take_snapshot(codesnap.highlight_mode_save_snapshot),
{ nargs = "*", range = "%" }
)