Skip to content

Commit

Permalink
Added feature to auto detect the OS end-of-line (EOL) character.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rohit Bhati committed Sep 2, 2024
1 parent f451f89 commit 3b1bcc2
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { errorMarkerEffect } from './extensions/errorMarker';
import { currentQueryHighlighterEffect } from './extensions/currentQueryHighlighter';
import { activeLineEffect, activeLineField } from './extensions/activeLineMarker';
import { clearBreakpoints, hasBreakpoint, toggleBreakpoint } from './extensions/breakpointGutter';
import { autoCompleteCompartment } from './extensions/extraStates';
import { autoCompleteCompartment, eolCompartment } from './extensions/extraStates';


function getAutocompLoading({ bottom, left }, dom) {
Expand All @@ -34,7 +34,7 @@ export default class CustomEditorView extends EditorView {
if(tillCursor) {
return this.state.sliceDoc(0, this.state.selection.main.head);
}
return this.state.doc.toString();
return this.state.sliceDoc();
}

/* Function to extract query based on position passed */
Expand Down Expand Up @@ -328,4 +328,14 @@ export default class CustomEditorView extends EditorView {
setQueryHighlightMark(from,to) {
this.dispatch({ effects: currentQueryHighlighterEffect.of({ from, to }) });
}

getLineSeparator(){
return this.state.field(EditorState.lineSeparator);
}

setLineSeparator(eol){
this.dispatch({
effects: eolCompartment.reconfigure(EditorState.lineSeparator.of(eol))
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ import CustomEditorView from '../CustomEditorView';
import breakpointGutter, { breakpointEffect } from '../extensions/breakpointGutter';
import activeLineExtn from '../extensions/activeLineMarker';
import currentQueryHighlighterExtn from '../extensions/currentQueryHighlighter';
import { autoCompleteCompartment, indentNewLine } from '../extensions/extraStates';
import { autoCompleteCompartment, eolCompartment, indentNewLine } from '../extensions/extraStates';
import { OS_EOL } from '../../../../../tools/sqleditor/static/js/components/QueryToolConstants';

const arrowRightHtml = ReactDOMServer.renderToString(<KeyboardArrowRightRoundedIcon style={{width: '16px'}} />);
const arrowDownHtml = ReactDOMServer.renderToString(<ExpandMoreRoundedIcon style={{width: '16px'}} />);
Expand Down Expand Up @@ -191,6 +192,7 @@ export default function Editor({
const state = EditorState.create({
extensions: [
...finalExtns,
eolCompartment.of([EditorState.lineSeparator.of(OS_EOL === 'crlf' ? '\r\n' : '\n' )]),
shortcuts.current.of([]),
configurables.current.of([]),
editableConfig.current.of([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export const indentNewLine = Facet.define({
});

export const autoCompleteCompartment = new Compartment();
export const eolCompartment = new Compartment();

Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const QUERY_TOOL_EVENTS = {
RESET_GRAPH_VISUALISER: 'RESET_GRAPH_VISUALISER',

GOTO_LAST_SCROLL: 'GOTO_LAST_SCROLL',
CHANGE_EOL: 'CHANGE_EOL'
};

export const CONNECTION_STATUS = {
Expand Down Expand Up @@ -105,4 +106,6 @@ export const PANELS = {
GRAPH_VISUALISER: 'id-graph-visualiser',
};

export const MAX_QUERY_LENGTH = 1000000;
export const MAX_QUERY_LENGTH = 1000000;

export const OS_EOL = navigator.platform === 'win32' ? 'crlf' : 'lf';
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export default function Query({onTextSelect}) {
const pgAdmin = usePgAdmin();
const preferencesStore = usePreferences();
const queryToolPref = queryToolCtx.preferences.sqleditor;

const highlightError = (cmObj, {errormsg: result, data}, executeCursor)=>{
let errorLineNo = 0,
startMarker = 0,
Expand Down Expand Up @@ -175,7 +174,6 @@ export default function Query({onTextSelect}) {
}
});


eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE, (fileName, storage)=>{
queryToolCtx.api.post(url_for('sqleditor.load_file'), {
'file_name': decodeURI(fileName),
Expand Down Expand Up @@ -288,6 +286,11 @@ export default function Query({onTextSelect}) {
editor.current.setValue(formattedSql);
}
});

eventBus.registerListener(QUERY_TOOL_EVENTS.CHANGE_EOL, (eol)=>{
editor.current.setLineSeparator(eol);
});

eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_TOGGLE_CASE, ()=>{
let selectedText = editor.current?.getSelection();
if (!selectedText) return;
Expand Down
290 changes: 175 additions & 115 deletions web/pgadmin/tools/sqleditor/static/js/components/sections/StatusBar.jsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,175 @@

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useEffect, useState, useContext } from 'react';
import { styled } from '@mui/material/styles';
import { Box } from '@mui/material';
import _ from 'lodash';
import { QUERY_TOOL_EVENTS } from '../QueryToolConstants';
import { useStopwatch } from '../../../../../../static/js/custom_hooks';
import { QueryToolEventsContext } from '../QueryToolComponent';
import gettext from 'sources/gettext';


const StyledBox = styled(Box)(({theme}) => ({
display: 'flex',
alignItems: 'center',
...theme.mixins.panelBorder.top,
flexWrap: 'wrap',
backgroundColor: theme.otherVars.editorToolbarBg,
userSelect: 'text',
'& .StatusBar-padding': {
padding: '2px 12px',
'& .StatusBar-mlAuto': {
marginLeft: 'auto',
},
'& .StatusBar-divider': {
...theme.mixins.panelBorder.right,
},
},
}));

export function StatusBar() {

const eventBus = useContext(QueryToolEventsContext);
const [position, setPosition] = useState([1, 1]);
const [lastTaskText, setLastTaskText] = useState(null);
const [rowsCount, setRowsCount] = useState([0, 0]);
const [selectedRowsCount, setSelectedRowsCount] = useState(0);
const [dataRowChangeCounts, setDataRowChangeCounts] = useState({
isDirty: false,
added: 0,
updated: 0,
deleted: 0,
});
const {seconds, minutes, hours, msec, start:startTimer, pause:pauseTimer, reset:resetTimer} = useStopwatch({});

useEffect(()=>{
eventBus.registerListener(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, (newPos)=>{
setPosition(newPos||[1, 1]);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, ()=>{
pauseTimer();
setLastTaskText(gettext('Query complete'));
});
eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_START, (taskText, startTime)=>{
resetTimer();
startTimer(startTime);
setLastTaskText(taskText);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_END, (taskText, endTime)=>{
pauseTimer(endTime);
setLastTaskText(taskText);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.ROWS_FETCHED, (fetched, total)=>{
setRowsCount([fetched||0, total||0]);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, (rows)=>{
setSelectedRowsCount(rows);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (_isDirty, dataChangeStore)=>{
setDataRowChangeCounts({
added: Object.keys(dataChangeStore.added||{}).length,
updated: Object.keys(dataChangeStore.updated||{}).length,
deleted: Object.keys(dataChangeStore.deleted||{}).length,
});
});
}, []);

let stagedText = '';
if(dataRowChangeCounts.added > 0) {
stagedText += ' ' + gettext('Added: %s', dataRowChangeCounts.added);
}
if(dataRowChangeCounts.updated > 0) {
stagedText += ' ' + gettext('Updated: %s', dataRowChangeCounts.updated);
}
if(dataRowChangeCounts.deleted > 0) {
stagedText += ' ' + gettext('Deleted: %s', dataRowChangeCounts.deleted);
}

return (
<StyledBox>
<Box className='StatusBar-padding StatusBar-divider'>{gettext('Total rows: %s of %s', rowsCount[0], rowsCount[1])}</Box>
{lastTaskText &&
<Box className='StatusBar-padding StatusBar-divider'>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
{!lastTaskText && !_.isNull(lastTaskText) &&
<Box className='StatusBar-padding StatusBar-divider'>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
{Boolean(selectedRowsCount) &&
<Box className='StatusBar-padding StatusBar-divider'>{gettext('Rows selected: %s',selectedRowsCount)}</Box>}
{stagedText &&
<Box className='StatusBar-padding StatusBar-divider'>
<span>{gettext('Changes staged: %s', stagedText)}</span>
</Box>
}
<Box className='StatusBar-padding StatusBar-mlAuto'>{gettext('Ln %s, Col %s', position[0], position[1])}</Box>
</StyledBox>
);
}

/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { useEffect, useState, useContext, useCallback } from 'react';
import { styled } from '@mui/material/styles';
import { Box, Tooltip } from '@mui/material';
import _ from 'lodash';
import { OS_EOL, QUERY_TOOL_EVENTS } from '../QueryToolConstants';
import { useStopwatch } from '../../../../../../static/js/custom_hooks';
import { QueryToolEventsContext } from '../QueryToolComponent';
import gettext from 'sources/gettext';
import { PgMenu, PgMenuItem } from '../../../../../../static/js/components/Menu';


const StyledBox = styled(Box)(({theme}) => ({
display: 'flex',
alignItems: 'center',
...theme.mixins.panelBorder.top,
flexWrap: 'wrap',
backgroundColor: theme.otherVars.editorToolbarBg,
userSelect: 'text',
'& .StatusBar-padding': {
padding: '2px 12px',
'& .StatusBar-mlAuto': {
marginLeft: 'auto',
},
'& .StatusBar-divider': {
...theme.mixins.panelBorder.right,
},
},
}));


export function StatusBar() {
const eventBus = useContext(QueryToolEventsContext);
const [position, setPosition] = useState([1, 1]);
const [lastTaskText, setLastTaskText] = useState(null);
const [rowsCount, setRowsCount] = useState([0, 0]);
const [selectedRowsCount, setSelectedRowsCount] = useState(0);
const [dataRowChangeCounts, setDataRowChangeCounts] = useState({
isDirty: false,
added: 0,
updated: 0,
deleted: 0,
});
const {seconds, minutes, hours, msec, start:startTimer, pause:pauseTimer, reset:resetTimer} = useStopwatch({});

const [checkedMenuItems, setCheckedMenuItems] = useState(OS_EOL);
const [openMenuName, setOpenMenuName] = useState(null);

const prevMenuOpenIdRef = React.useRef(null);
const eolMenuRef = React.useRef(null);

const toggleMenu = React.useCallback((e) => {
const name = e.currentTarget?.getAttribute('name');
setOpenMenuName(() => {
return prevMenuOpenIdRef.current === name ? null : name;
});
prevMenuOpenIdRef.current = null;
}, []);

const onMenuClose = React.useCallback(() => {
prevMenuOpenIdRef.current = openMenuName;
setTimeout(() => {
prevMenuOpenIdRef.current = null;
}, 300);
setOpenMenuName(null);
}, [openMenuName]);

const checkMenuClick = useCallback((e)=>{
setCheckedMenuItems(e.value);
handleEndOfLineChange(e.value);
onMenuClose();
}, []);

const handleEndOfLineChange = (val) => {
const eol = val === 'crlf' ? '\r\n' : '\n';
eventBus.fireEvent(QUERY_TOOL_EVENTS.CHANGE_EOL, eol);
};

useEffect(()=>{
eventBus.registerListener(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, (newPos)=>{
setPosition(newPos||[1, 1]);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, ()=>{
pauseTimer();
setLastTaskText(gettext('Query complete'));
});
eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_START, (taskText, startTime)=>{
resetTimer();
startTimer(startTime);
setLastTaskText(taskText);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_END, (taskText, endTime)=>{
pauseTimer(endTime);
setLastTaskText(taskText);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.ROWS_FETCHED, (fetched, total)=>{
setRowsCount([fetched||0, total||0]);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, (rows)=>{
setSelectedRowsCount(rows);
});
eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (_isDirty, dataChangeStore)=>{
setDataRowChangeCounts({
added: Object.keys(dataChangeStore.added||{}).length,
updated: Object.keys(dataChangeStore.updated||{}).length,
deleted: Object.keys(dataChangeStore.deleted||{}).length,
});
});
}, []);

let stagedText = '';
if(dataRowChangeCounts.added > 0) {
stagedText += ' ' + gettext('Added: %s', dataRowChangeCounts.added);
}
if(dataRowChangeCounts.updated > 0) {
stagedText += ' ' + gettext('Updated: %s', dataRowChangeCounts.updated);
}
if(dataRowChangeCounts.deleted > 0) {
stagedText += ' ' + gettext('Deleted: %s', dataRowChangeCounts.deleted);
}

return (
<StyledBox>
<Box className='StatusBar-padding StatusBar-divider'>{gettext('Total rows: %s of %s', rowsCount[0], rowsCount[1])}</Box>
{lastTaskText &&
<Box className='StatusBar-padding StatusBar-divider'>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
{!lastTaskText && !_.isNull(lastTaskText) &&
<Box className='StatusBar-padding StatusBar-divider'>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
{Boolean(selectedRowsCount) &&
<Box className='StatusBar-padding StatusBar-divider'>{gettext('Rows selected: %s',selectedRowsCount)}</Box>}
{stagedText &&
<Box className='StatusBar-padding StatusBar-divider'>
<span>{gettext('Changes staged: %s', stagedText)}</span>
</Box>
}
<Box className="StatusBar-padding StatusBar-mlAuto">
<Tooltip title="Select EOL Sequence">
<span
onClick={toggleMenu}
ref={eolMenuRef}
name="menu-eoloptions"
style={{
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
fontSize: 'inherit',
}}
>
{checkedMenuItems.toUpperCase()}
</span>
</Tooltip>
<PgMenu
anchorRef={eolMenuRef}
open={openMenuName=='menu-eoloptions'}
onClose={onMenuClose}
label={gettext('EOL Options Menu')}
>
<PgMenuItem hasCheck value="lf" checked={checkedMenuItems === 'lf'} onClick={checkMenuClick}>{gettext('LF')}</PgMenuItem>
<PgMenuItem hasCheck value="crlf" checked={checkedMenuItems === 'crlf'} onClick={checkMenuClick}>{gettext('CRLF')}</PgMenuItem>
</PgMenu>
</Box>
<Box className='StatusBar-padding StatusBar-mlAuto'>{gettext('Ln %s, Col %s', position[0], position[1])}</Box>
</StyledBox>
);
}

0 comments on commit 3b1bcc2

Please sign in to comment.