mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Overhaul the query history tab to allow browsing of the history and full query text. Fixes #2282
Patch by Joao and the team at Pivotal.
This commit is contained in:
parent
e413186d23
commit
7f55412059
@ -38,6 +38,7 @@
|
||||
"moment": "^2.18.1",
|
||||
"react": "file:../web/pgadmin/static/vendor/react",
|
||||
"react-dom": "file:../web/pgadmin/static/vendor/react-dom",
|
||||
"react-split-pane": "^0.1.63",
|
||||
"requirejs": "~2.3.3",
|
||||
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.7",
|
||||
"underscore": "~1.8.3",
|
||||
|
@ -75,10 +75,18 @@ class QueryToolJourneyTest(BaseFeatureTest):
|
||||
self._execute_query("SELECT * FROM shoes")
|
||||
|
||||
self.page.click_tab("History")
|
||||
history_element = self.page.find_by_id("history_grid")
|
||||
history_element = self.page.find_by_id("query_list")
|
||||
self.assertIn("SELECT * FROM test_table", history_element.text)
|
||||
self.assertIn("SELECT * FROM shoes", history_element.text)
|
||||
|
||||
history_detail = self.page.find_by_id("query_detail")
|
||||
self.assertIn("SELECT * FROM shoes", history_detail.text)
|
||||
|
||||
old_history_list_element = self.page.find_by_xpath("//*[@id='query_list']/ul/li[2]")
|
||||
self.page.click_element(old_history_list_element)
|
||||
history_detail = self.page.find_by_id("query_detail")
|
||||
self.assertIn("SELECT * FROM test_table", history_detail.text)
|
||||
|
||||
def __clear_query_tool(self):
|
||||
self.page.click_element(self.page.find_by_xpath("//*[@id='btn-edit']"))
|
||||
self.page.click_modal('Yes')
|
||||
|
58
web/pgadmin/static/css/bootstrap.overrides.css
vendored
58
web/pgadmin/static/css/bootstrap.overrides.css
vendored
@ -55,12 +55,6 @@ iframe {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Ensure the codemirror editor displays full height gutters when resized */
|
||||
.CodeMirror, .CodeMirror-gutter {
|
||||
height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
}
|
||||
|
||||
/* Padding for the treeview */
|
||||
.browser-browser-pane {
|
||||
padding-left: 0;
|
||||
@ -926,22 +920,6 @@ td.edit-cell.editable.sortable.renderable.editor {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove border right from "CodeMirror-gutters"
|
||||
* class becuase its height doesn't increase
|
||||
* with every new line added, instead add border
|
||||
* right to ".CodeMirror-linenumber" class which
|
||||
* adds a new div for every new line added
|
||||
*/
|
||||
.sql_field_layout .CodeMirror-gutters {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sql_field_layout .CodeMirror-linenumber {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.tab-pane.SQL {
|
||||
padding: 0px 7px 0px 0px;
|
||||
height: 100%;
|
||||
@ -1420,22 +1398,6 @@ table.backgrid {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* class to disable Codemirror editor */
|
||||
.cm_disabled {
|
||||
background: #EEEEEE;
|
||||
}
|
||||
|
||||
/* make syntax-highlighting bold */
|
||||
.cm-s-default .cm-keyword {
|
||||
font-weight: 600;
|
||||
color: #908;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
font-weight: 600;
|
||||
color: #964;
|
||||
}
|
||||
|
||||
.pgadmin-controls.sql_field_layout {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -1443,10 +1405,6 @@ table.backgrid {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pgadmin-controls.sql_field_layout .CodeMirror {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.pgadmin-controls.focused {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
@ -1502,22 +1460,6 @@ body {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* Codemirror buttons */
|
||||
.CodeMirror-dialog button {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: white;
|
||||
font-size: 70%;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dashboard-tab-container table,
|
||||
.pg-panel-statistics-container table,
|
||||
.pg-panel-depends-container table,
|
||||
|
@ -3,3 +3,69 @@
|
||||
color: #333333 !important;
|
||||
background-color: #e8e8e8 !important;
|
||||
}
|
||||
|
||||
/* Ensure the codemirror editor displays full height gutters when resized */
|
||||
.CodeMirror, .CodeMirror-gutter {
|
||||
height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove border right from "CodeMirror-gutters"
|
||||
* class becuase its height doesn't increase
|
||||
* with every new line added, instead add border
|
||||
* right to ".CodeMirror-linenumber" class which
|
||||
* adds a new div for every new line added
|
||||
*/
|
||||
.sql_field_layout .CodeMirror-gutters {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sql_field_layout .CodeMirror-linenumber {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
/* class to disable Codemirror editor */
|
||||
.cm_disabled {
|
||||
background: #EEEEEE;
|
||||
}
|
||||
|
||||
/* make syntax-highlighting bold */
|
||||
.cm-s-default .cm-keyword {
|
||||
font-weight: 600;
|
||||
color: #908;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
font-weight: 600;
|
||||
color: #964;
|
||||
}
|
||||
|
||||
.pgadmin-controls.sql_field_layout .CodeMirror {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
/* Codemirror buttons */
|
||||
.CodeMirror-dialog button {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: white;
|
||||
font-size: 70%;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#history-detail-query .CodeMirror {
|
||||
border: 1px solid #CCCCCC;
|
||||
background-color: #F7F7F7;
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
position: absolute;
|
||||
}
|
@ -781,3 +781,35 @@ lgg-el-container[el=md] .pg-el-lg-8,
|
||||
.user-language select{
|
||||
height: 25px !important;
|
||||
}
|
||||
|
||||
.Resizer {
|
||||
background: #dddddd;
|
||||
z-index: 1;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.Resizer.horizontal {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
cursor: ew-resize;
|
||||
border-bottom: 1px solid #aaaaaa;
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 4px;
|
||||
cursor: ew-resize;
|
||||
border-right: 1px solid #aaaaaa;
|
||||
}
|
||||
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
@ -7,8 +7,8 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import historyCollection from './history_collection';
|
||||
import HistoryCollection from './history_collection';
|
||||
|
||||
export {
|
||||
historyCollection,
|
||||
HistoryCollection,
|
||||
};
|
||||
|
61
web/pgadmin/static/jsx/history/detail/code_mirror.jsx
Normal file
61
web/pgadmin/static/jsx/history/detail/code_mirror.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import $ from '../../../vendor/jquery/jquery-1.11.2';
|
||||
import code_mirror from '../../../vendor/codemirror/lib/codemirror';
|
||||
|
||||
export default class CodeMirror extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
shouldHydrate: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.editor = code_mirror(this.container);
|
||||
this.hydrateInterval = setInterval(this.hydrateWhenBecomesVisible.bind(this), 100);
|
||||
this.hydrate(this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.hydrateInterval);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.hydrate(nextProps);
|
||||
}
|
||||
|
||||
hydrateWhenBecomesVisible() {
|
||||
const isVisible = $(this.container).is(':visible');
|
||||
|
||||
if (isVisible && this.state.shouldHydrate) {
|
||||
this.hydrate(this.props);
|
||||
this.setState({shouldHydrate: false});
|
||||
} else if (!isVisible) {
|
||||
this.setState({shouldHydrate: true});
|
||||
}
|
||||
}
|
||||
|
||||
hydrate(props) {
|
||||
Object.keys(props.options || {}).forEach(key => this.editor.setOption(key, props.options[key]));
|
||||
|
||||
this.editor.setValue(props.value || '');
|
||||
this.editor.refresh();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div ref={(self) => this.container = self}/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Shapes from '../../react_shapes';
|
||||
import NonSelectableElementStyle from '../../styles/non_selectable';
|
||||
import MessageHeaderStyle from '../../styles/header_label';
|
||||
|
||||
const containerStyle = {
|
||||
flex: '2 2 0%',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
};
|
||||
|
||||
const messageContainerStyle = {
|
||||
flex: '0 1 auto',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
const errorMessageStyle = {
|
||||
border: '0',
|
||||
paddingLeft: '0',
|
||||
backgroundColor: '#ffffff',
|
||||
fontSize: '13px',
|
||||
position: 'absolute',
|
||||
};
|
||||
|
||||
const messageLabelStyle = _.extend({flex: '0 0 auto'},
|
||||
MessageHeaderStyle,
|
||||
NonSelectableElementStyle);
|
||||
|
||||
export default class HistoryDetailMessage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<div style={messageLabelStyle}>
|
||||
Messages
|
||||
</div>
|
||||
<div style={messageContainerStyle}>
|
||||
<pre style={errorMessageStyle}>
|
||||
{this.props.historyEntry.message}
|
||||
</pre>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailMessage.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import Shapes from '../../react_shapes';
|
||||
import HeaderDescriptionStyle from '../../styles/header_label';
|
||||
|
||||
const queryMetaDataStyle = {
|
||||
flex: 1,
|
||||
};
|
||||
const headerStyle = {
|
||||
display: 'flex',
|
||||
};
|
||||
const headerValueStyle = {
|
||||
display: 'block',
|
||||
fontSize: '14px',
|
||||
};
|
||||
|
||||
export default class HistoryDetailMetadata extends React.Component {
|
||||
|
||||
formatDate(date) {
|
||||
return (moment(date).format('M-D-YY HH:mm:ss'));
|
||||
}
|
||||
|
||||
queryMetaData(data, description) {
|
||||
return <div style={queryMetaDataStyle}>
|
||||
<span style={headerValueStyle}>
|
||||
{data}
|
||||
</span>
|
||||
<span style={HeaderDescriptionStyle}>
|
||||
{description}
|
||||
</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={headerStyle}>
|
||||
{this.queryMetaData(this.formatDate(this.props.historyEntry.start_time), 'Date')}
|
||||
{this.queryMetaData(this.props.historyEntry.row_affected.toLocaleString(), 'Rows Affected')}
|
||||
{this.queryMetaData(this.props.historyEntry.total_time, 'Duration')}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailMetadata.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import '../../../vendor/codemirror/mode/sql/sql';
|
||||
|
||||
import CodeMirror from './code_mirror';
|
||||
import Shapes from '../../react_shapes';
|
||||
|
||||
export default class HistoryDetailQuery extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div id="history-detail-query">
|
||||
<CodeMirror
|
||||
value={this.props.historyEntry.query}
|
||||
options={{
|
||||
mode: 'text/x-pgsql',
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailQuery.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
|
||||
import update from 'immutability-helper';
|
||||
import {errorStyle} from '../../styles/history_entry_styles';
|
||||
|
||||
export default class QueryHistoryErrorEntry extends QueryHistoryVanillaEntry {
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
outerDivStyle: update(this.state.outerDivStyle, {$merge: errorStyle}),
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
|
||||
import update from 'immutability-helper';
|
||||
import {selectedFontStyle, selectedOuterStyle} from '../../styles/history_entry_styles';
|
||||
|
||||
export default class QueryHistorySelectedEntry extends QueryHistoryVanillaEntry {
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedOuterStyle}),
|
||||
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import QueryHistoryVanillaEntry from './query_history_vanilla_entry';
|
||||
import update from 'immutability-helper';
|
||||
import {selectedFontStyle, selectedOuterStyle, selectedErrorBgColor} from '../../styles/history_entry_styles';
|
||||
|
||||
export default class QueryHistorySelectedErrorEntry extends QueryHistoryVanillaEntry {
|
||||
componentWillMount() {
|
||||
let selectedErrorStyle = update(selectedOuterStyle, {$merge: selectedErrorBgColor});
|
||||
this.setState({
|
||||
outerDivStyle: update(this.state.outerDivStyle, {$merge: selectedErrorStyle}),
|
||||
secondLineStyle: update(this.state.secondLineStyle, {$merge: selectedFontStyle}),
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import Shapes from '../../react_shapes';
|
||||
import {plainOuterDivStyle, plainSecondLineStyle, sqlStyle, timestampStyle} from '../../styles/history_entry_styles';
|
||||
|
||||
export default class QueryHistoryVanillaEntry extends React.Component {
|
||||
formatDate(date) {
|
||||
return (moment(date).format('MMM D YYYY [–] HH:mm:ss'));
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
outerDivStyle: plainOuterDivStyle,
|
||||
secondLineStyle: plainSecondLineStyle,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={this.state.outerDivStyle}>
|
||||
<div style={sqlStyle}>
|
||||
{this.props.historyEntry.query}
|
||||
</div>
|
||||
<div style={this.state.secondLineStyle}>
|
||||
<div style={timestampStyle}>
|
||||
{this.formatDate(this.props.historyEntry.start_time)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryVanillaEntry.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
isSelected: React.PropTypes.bool,
|
||||
};
|
@ -8,8 +8,17 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import QueryHistoryEntry from './query_history_entry';
|
||||
import QueryHistoryDetail from './query_history_detail';
|
||||
import Shapes from '../react_shapes';
|
||||
|
||||
const queryEntryListDivStyle = {
|
||||
overflowY: 'auto',
|
||||
};
|
||||
const queryDetailDivStyle = {
|
||||
display: 'flex',
|
||||
};
|
||||
const liStyle = {
|
||||
borderBottom: '1px solid #cccccc',
|
||||
};
|
||||
@ -21,29 +30,67 @@ export default class QueryHistory extends React.Component {
|
||||
|
||||
this.state = {
|
||||
history: [],
|
||||
selectedEntry: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({history: this.props.historyCollection.historyList});
|
||||
this.props.historyCollection.onChange((historyList) => this.setState({history: historyList}));
|
||||
this.resetCurrentHistoryDetail(this.props.historyCollection.historyList);
|
||||
this.props.historyCollection.onChange((historyList) => {
|
||||
this.resetCurrentHistoryDetail(historyList);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.resetCurrentHistoryDetail(this.state.history);
|
||||
}
|
||||
|
||||
getCurrentHistoryDetail() {
|
||||
return this.state.currentHistoryDetail;
|
||||
}
|
||||
|
||||
setCurrentHistoryDetail(index, historyList) {
|
||||
this.setState({
|
||||
history: historyList,
|
||||
currentHistoryDetail: this.retrieveOrderedHistory().value()[index],
|
||||
selectedEntry: index,
|
||||
});
|
||||
}
|
||||
|
||||
resetCurrentHistoryDetail(historyList) {
|
||||
this.setCurrentHistoryDetail(0, historyList);
|
||||
}
|
||||
|
||||
retrieveOrderedHistory() {
|
||||
return _.chain(this.state.history)
|
||||
.sortBy(historyEntry => historyEntry.start_time)
|
||||
.reverse();
|
||||
}
|
||||
|
||||
onClickHandler(index) {
|
||||
this.setCurrentHistoryDetail(index, this.state.history);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ul>
|
||||
{_.chain(this.state.history)
|
||||
.sortBy(historyEntry => historyEntry.start_time)
|
||||
.reverse()
|
||||
.map((entry, index) =>
|
||||
<li key={index} style={liStyle}>
|
||||
<QueryHistoryEntry historyEntry={entry}/>
|
||||
</li>)
|
||||
.value()
|
||||
}
|
||||
</ul>;
|
||||
return (
|
||||
<SplitPane defaultSize="50%" split="vertical" pane1Style={queryEntryListDivStyle}
|
||||
pane2Style={queryDetailDivStyle}>
|
||||
<div id='query_list'>
|
||||
<ul>
|
||||
{this.retrieveOrderedHistory()
|
||||
.map((entry, index) =>
|
||||
<li key={index} style={liStyle} onClick={this.onClickHandler.bind(this, index)}>
|
||||
<QueryHistoryEntry historyEntry={entry} isSelected={index == this.state.selectedEntry}/>
|
||||
</li>)
|
||||
.value()
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<QueryHistoryDetail historyEntry={this.getCurrentHistoryDetail()}/>
|
||||
</SplitPane>);
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistory.propTypes = {
|
||||
historyCollection: React.PropTypes.object.isRequired,
|
||||
historyCollection: Shapes.historyCollectionClass.isRequired,
|
||||
};
|
75
web/pgadmin/static/jsx/history/query_history_detail.jsx
Normal file
75
web/pgadmin/static/jsx/history/query_history_detail.jsx
Normal file
@ -0,0 +1,75 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import HistoryDetailMetadata from './detail/history_detail_metadata';
|
||||
import HistoryDetailQuery from './detail/history_detail_query';
|
||||
import HistoryDetailMessage from './detail/history_detail_message';
|
||||
import Shapes from '../react_shapes';
|
||||
|
||||
const outerStyle = {
|
||||
width: '100%',
|
||||
paddingTop: '10px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
|
||||
const detailVerticalTop = {
|
||||
flex: 1,
|
||||
padding: '0 10px',
|
||||
};
|
||||
|
||||
const detailVerticalMiddle = {
|
||||
flex: 5,
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px',
|
||||
height: 0,
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
const hrStyle = {
|
||||
borderColor: '#cccccc',
|
||||
marginTop: '11px',
|
||||
marginBottom: '8px',
|
||||
};
|
||||
|
||||
const detailVerticalBottom = {
|
||||
flex: 2,
|
||||
display: 'flex',
|
||||
paddingLeft: '10px',
|
||||
};
|
||||
|
||||
export default class QueryHistoryDetail extends React.Component {
|
||||
|
||||
render() {
|
||||
if (!_.isUndefined(this.props.historyEntry)) {
|
||||
return (
|
||||
<div id='query_detail' style={outerStyle}>
|
||||
<div style={detailVerticalTop}>
|
||||
<HistoryDetailMetadata {...this.props} />
|
||||
</div>
|
||||
<div style={detailVerticalMiddle}>
|
||||
<HistoryDetailQuery {...this.props}/>
|
||||
</div>
|
||||
<div>
|
||||
<hr style={hrStyle}/>
|
||||
</div>
|
||||
<div style={detailVerticalBottom}>
|
||||
<HistoryDetailMessage {...this.props}/>
|
||||
</div>
|
||||
</div>);
|
||||
} else {
|
||||
return <p></p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryDetail.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -1,93 +1,42 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import update from 'immutability-helper';
|
||||
import moment from 'moment';
|
||||
|
||||
const outerDivStyle = {
|
||||
paddingLeft: '10px',
|
||||
fontFamily: 'monospace',
|
||||
paddingRight: '20px',
|
||||
fontSize: '14px',
|
||||
backgroundColor: '#FFF',
|
||||
};
|
||||
const sqlStyle = {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'auto',
|
||||
};
|
||||
const secondLineStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '13px',
|
||||
color: '#888888',
|
||||
};
|
||||
const timestampStyle = {
|
||||
alignSelf: 'flex-start',
|
||||
};
|
||||
const rowsAffectedStyle = {
|
||||
alignSelf: 'flex-end',
|
||||
};
|
||||
const errorMessageStyle = {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'auto',
|
||||
fontSize: '13px',
|
||||
color: '#888888',
|
||||
};
|
||||
import Shapes from '../react_shapes';
|
||||
import QueryHistoryErrorEntry from './entry/query_history_error_entry';
|
||||
import QueryHistorySelectedErrorEntry from './entry/query_history_selected_error_entry';
|
||||
import QueryHistorySelectedEntry from './entry/query_history_selected_entry';
|
||||
import QueryHistoryVanillaEntry from './entry/query_history_vanilla_entry';
|
||||
|
||||
export default class QueryHistoryEntry extends React.Component {
|
||||
formatDate(date) {
|
||||
return (moment(date).format('MMM D YYYY [–] HH:mm:ss'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={this.queryEntryBackgroundColor()}>
|
||||
<div style={sqlStyle}>
|
||||
{this.props.historyEntry.query}
|
||||
</div>
|
||||
<div style={secondLineStyle}>
|
||||
<div style={timestampStyle}>
|
||||
{this.formatDate(this.props.historyEntry.start_time)} /
|
||||
total time: {this.props.historyEntry.total_time}
|
||||
</div>
|
||||
<div style={rowsAffectedStyle}>
|
||||
{this.props.historyEntry.row_affected} rows affected
|
||||
</div>
|
||||
</div>
|
||||
<div style={errorMessageStyle}>
|
||||
{this.props.historyEntry.message}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (this.hasError()) {
|
||||
if (this.props.isSelected) {
|
||||
return <QueryHistorySelectedErrorEntry {...this.props}/>;
|
||||
} else {
|
||||
return <QueryHistoryErrorEntry {...this.props}/>;
|
||||
}
|
||||
} else {
|
||||
if (this.props.isSelected) {
|
||||
return <QueryHistorySelectedEntry {...this.props}/>;
|
||||
} else {
|
||||
return <QueryHistoryVanillaEntry {...this.props}/>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queryEntryBackgroundColor() {
|
||||
if (!this.props.historyEntry.status) {
|
||||
return update(outerDivStyle, {$merge: {backgroundColor: '#F7D0D5'}});
|
||||
}
|
||||
return outerDivStyle;
|
||||
hasError() {
|
||||
return !this.props.historyEntry.status;
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryEntry.propTypes = {
|
||||
historyEntry: React.PropTypes.shape({
|
||||
query: React.PropTypes.string,
|
||||
start_time: React.PropTypes.instanceOf(Date),
|
||||
status: React.PropTypes.bool,
|
||||
total_time: React.PropTypes.string,
|
||||
row_affected: React.PropTypes.int,
|
||||
message: React.PropTypes.string,
|
||||
}),
|
||||
historyEntry: Shapes.historyDetail,
|
||||
isSelected: React.PropTypes.bool,
|
||||
};
|
31
web/pgadmin/static/jsx/react_shapes.jsx
Normal file
31
web/pgadmin/static/jsx/react_shapes.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
|
||||
let historyDetail =
|
||||
React.PropTypes.shape({
|
||||
query: React.PropTypes.string,
|
||||
start_time: React.PropTypes.instanceOf(Date),
|
||||
status: React.PropTypes.bool,
|
||||
total_time: React.PropTypes.string,
|
||||
row_affected: React.PropTypes.int,
|
||||
message: React.PropTypes.string,
|
||||
});
|
||||
|
||||
let historyCollectionClass =
|
||||
React.PropTypes.shape({
|
||||
historyList: React.PropTypes.array.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
});
|
||||
|
||||
export default {
|
||||
historyDetail,
|
||||
historyCollectionClass,
|
||||
};
|
14
web/pgadmin/static/jsx/styles/header_label.js
Normal file
14
web/pgadmin/static/jsx/styles/header_label.js
Normal file
@ -0,0 +1,14 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export default {
|
||||
display: 'block',
|
||||
fontSize: '12px',
|
||||
color: '#888888',
|
||||
};
|
57
web/pgadmin/static/jsx/styles/history_entry_styles.js
Normal file
57
web/pgadmin/static/jsx/styles/history_entry_styles.js
Normal file
@ -0,0 +1,57 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import update from 'immutability-helper';
|
||||
|
||||
export const plainOuterDivStyle = {
|
||||
paddingLeft: '8px',
|
||||
paddingRight: '18px',
|
||||
paddingTop: '-2px',
|
||||
paddingBottom: '-2px',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '14px',
|
||||
backgroundColor: '#FFF',
|
||||
border: '2px solid transparent',
|
||||
marginLeft: '1px',
|
||||
};
|
||||
|
||||
export const sqlStyle = {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'auto',
|
||||
};
|
||||
|
||||
export const plainSecondLineStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '13px',
|
||||
color: '#888888',
|
||||
};
|
||||
|
||||
export const timestampStyle = {
|
||||
alignSelf: 'flex-start',
|
||||
};
|
||||
|
||||
export const selectedFontStyle = {
|
||||
color: '#2c76b4',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
|
||||
export const selectedOuterStyle = update(selectedFontStyle, {
|
||||
$merge: {
|
||||
border: '2px solid #2c76b4',
|
||||
backgroundColor: '#e7f2ff',
|
||||
},
|
||||
});
|
||||
|
||||
export const errorStyle = {backgroundColor: '#F7D0D5'};
|
||||
|
||||
export const selectedErrorBgColor = {backgroundColor: '#DCC4D1'};
|
18
web/pgadmin/static/jsx/styles/non_selectable.js
Normal file
18
web/pgadmin/static/jsx/styles/non_selectable.js
Normal file
@ -0,0 +1,18 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export default {
|
||||
WebkitTouchCallout: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
KhtmlUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
userSelect: 'none',
|
||||
cursor: 'default',
|
||||
};
|
@ -46,12 +46,17 @@
|
||||
|
||||
// Rough heuristic to try and detect lines that are part of multi-line string
|
||||
function probablyInsideString(cm, pos, line) {
|
||||
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line)
|
||||
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
|
||||
}
|
||||
|
||||
function getMode(cm, pos) {
|
||||
var mode = cm.getMode()
|
||||
return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var self = this, mode = getMode(self, from);
|
||||
var firstLine = self.getLine(from.line);
|
||||
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
|
||||
|
||||
@ -95,7 +100,7 @@
|
||||
|
||||
CodeMirror.defineExtension("blockComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var self = this, mode = getMode(self, from);
|
||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) {
|
||||
@ -103,6 +108,7 @@
|
||||
self.lineComment(from, to, options);
|
||||
return;
|
||||
}
|
||||
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
|
||||
|
||||
var end = Math.min(to.line, self.lastLine());
|
||||
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
|
||||
@ -128,7 +134,7 @@
|
||||
|
||||
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = self.getModeAt(from);
|
||||
var self = this, mode = getMode(self, from);
|
||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
|
||||
|
||||
// Try finding line comments
|
||||
@ -140,7 +146,7 @@
|
||||
var line = self.getLine(i);
|
||||
var found = line.indexOf(lineString);
|
||||
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
|
||||
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
|
||||
if (found == -1 && nonWS.test(line)) break lineComment;
|
||||
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
|
||||
lines.push(line);
|
||||
}
|
||||
@ -162,15 +168,19 @@
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) return false;
|
||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
|
||||
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
|
||||
var startLine = self.getLine(start), open = startLine.indexOf(startString)
|
||||
if (open == -1) return false
|
||||
var endLine = end == start ? startLine : self.getLine(end)
|
||||
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
|
||||
if (close == -1 && start != end) {
|
||||
endLine = self.getLine(--end);
|
||||
close = endLine.lastIndexOf(endString);
|
||||
close = endLine.indexOf(endString);
|
||||
}
|
||||
if (open == -1 || close == -1 ||
|
||||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
|
||||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
|
||||
var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
|
||||
if (close == -1 ||
|
||||
!/comment/.test(self.getTokenTypeAt(insideStart)) ||
|
||||
!/comment/.test(self.getTokenTypeAt(insideEnd)) ||
|
||||
self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
|
||||
return false;
|
||||
|
||||
// Avoid killing block comments completely outside the selection.
|
||||
|
@ -38,6 +38,9 @@
|
||||
var height = (options && options.height) || node.offsetHeight;
|
||||
this._setSize(null, info.heightLeft -= height);
|
||||
info.panels++;
|
||||
if (options.stable && isAtTop(this, node))
|
||||
this.scrollTo(null, this.getScrollInfo().top + height)
|
||||
|
||||
return new Panel(this, node, options, height);
|
||||
});
|
||||
|
||||
@ -54,6 +57,8 @@
|
||||
this.cleared = true;
|
||||
var info = this.cm.state.panels;
|
||||
this.cm._setSize(null, info.heightLeft += this.height);
|
||||
if (this.options.stable && isAtTop(this.cm, this.node))
|
||||
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
|
||||
info.wrapper.removeChild(this.node);
|
||||
if (--info.panels == 0) removePanels(this.cm);
|
||||
};
|
||||
@ -61,7 +66,7 @@
|
||||
Panel.prototype.changed = function(height) {
|
||||
var newHeight = height == null ? this.node.offsetHeight : height;
|
||||
var info = this.cm.state.panels;
|
||||
this.cm._setSize(null, info.height += (newHeight - this.height));
|
||||
this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
|
||||
this.height = newHeight;
|
||||
};
|
||||
|
||||
@ -109,4 +114,10 @@
|
||||
cm.setSize = cm._setSize;
|
||||
cm.setSize();
|
||||
}
|
||||
|
||||
function isAtTop(cm, dom) {
|
||||
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
|
||||
if (sibling == cm.getWrapperElement()) return true
|
||||
return false
|
||||
}
|
||||
});
|
||||
|
@ -11,30 +11,26 @@
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("rulers", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
clearRulers(cm);
|
||||
cm.off("refresh", refreshRulers);
|
||||
CodeMirror.defineOption("rulers", false, function(cm, val) {
|
||||
if (cm.state.rulerDiv) {
|
||||
cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
|
||||
cm.state.rulerDiv = null
|
||||
cm.off("refresh", drawRulers)
|
||||
}
|
||||
if (val && val.length) {
|
||||
setRulers(cm);
|
||||
cm.on("refresh", refreshRulers);
|
||||
cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
|
||||
cm.state.rulerDiv.className = "CodeMirror-rulers"
|
||||
drawRulers(cm)
|
||||
cm.on("refresh", drawRulers)
|
||||
}
|
||||
});
|
||||
|
||||
function clearRulers(cm) {
|
||||
for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
|
||||
var node = cm.display.lineSpace.childNodes[i];
|
||||
if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
function setRulers(cm) {
|
||||
function drawRulers(cm) {
|
||||
cm.state.rulerDiv.textContent = ""
|
||||
var val = cm.getOption("rulers");
|
||||
var cw = cm.defaultCharWidth();
|
||||
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
|
||||
var minH = cm.display.scroller.offsetHeight + 30;
|
||||
cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
var elt = document.createElement("div");
|
||||
elt.className = "CodeMirror-ruler";
|
||||
@ -49,15 +45,7 @@
|
||||
if (conf.width) elt.style.borderLeftWidth = conf.width;
|
||||
}
|
||||
elt.style.left = (left + col * cw) + "px";
|
||||
elt.style.top = "-50px";
|
||||
elt.style.bottom = "-20px";
|
||||
elt.style.minHeight = minH + "px";
|
||||
cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
|
||||
cm.state.rulerDiv.appendChild(elt)
|
||||
}
|
||||
}
|
||||
|
||||
function refreshRulers(cm) {
|
||||
clearRulers(cm);
|
||||
setRulers(cm);
|
||||
}
|
||||
});
|
||||
|
@ -45,7 +45,7 @@
|
||||
|
||||
function getConfig(cm) {
|
||||
var deflt = cm.state.closeBrackets;
|
||||
if (!deflt) return null;
|
||||
if (!deflt || deflt.override) return deflt;
|
||||
var mode = cm.getModeAt(cm.getCursor());
|
||||
return mode.closeBrackets || deflt;
|
||||
}
|
||||
@ -109,14 +109,16 @@
|
||||
var ranges = cm.listSelections();
|
||||
var opening = pos % 2 == 0;
|
||||
|
||||
var type, next;
|
||||
var type;
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i], cur = range.head, curType;
|
||||
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
||||
if (opening && !range.empty()) {
|
||||
curType = "surround";
|
||||
} else if ((identical || !opening) && next == ch) {
|
||||
if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
||||
if (identical && stringStartsAfter(cm, cur))
|
||||
curType = "both";
|
||||
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
||||
curType = "skipThree";
|
||||
else
|
||||
curType = "skip";
|
||||
@ -183,7 +185,7 @@
|
||||
function enteringString(cm, pos, ch) {
|
||||
var line = cm.getLine(pos.line);
|
||||
var token = cm.getTokenAt(pos);
|
||||
if (/\bstring2?\b/.test(token.type)) return false;
|
||||
if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false;
|
||||
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
|
||||
stream.pos = stream.start = token.start;
|
||||
for (;;) {
|
||||
@ -192,4 +194,9 @@
|
||||
stream.start = stream.pos;
|
||||
}
|
||||
}
|
||||
|
||||
function stringStartsAfter(cm, pos) {
|
||||
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
|
||||
return /\bstring/.test(token.type) && token.start == pos.ch
|
||||
}
|
||||
});
|
||||
|
@ -11,8 +11,8 @@
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,
|
||||
emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/,
|
||||
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
|
||||
emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
|
||||
unorderedListRE = /[*+-]\s/;
|
||||
|
||||
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
|
||||
@ -30,7 +30,7 @@
|
||||
return;
|
||||
}
|
||||
if (emptyListRE.test(line)) {
|
||||
cm.replaceRange("", {
|
||||
if (!/>\s*$/.test(line)) cm.replaceRange("", {
|
||||
line: pos.line, ch: 0
|
||||
}, {
|
||||
line: pos.line, ch: pos.ch + 1
|
||||
@ -39,7 +39,7 @@
|
||||
} else {
|
||||
var indent = match[1], after = match[5];
|
||||
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
|
||||
? match[2]
|
||||
? match[2].replace("x", " ")
|
||||
: (parseInt(match[3], 10) + 1) + match[4];
|
||||
|
||||
replacements[i] = "\n" + indent + bullet + after;
|
||||
|
@ -102,8 +102,10 @@
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init)
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.off("cursorActivity", doMatchBrackets);
|
||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
||||
}
|
||||
if (val) {
|
||||
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
||||
cm.on("cursorActivity", doMatchBrackets);
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
|
||||
var line = start.line, lineText = cm.getLine(line);
|
||||
var startCh, tokenType;
|
||||
var tokenType;
|
||||
|
||||
function findOpening(openCh) {
|
||||
for (var at = start.ch, pass = 0;;) {
|
||||
@ -72,15 +72,15 @@ CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
||||
}
|
||||
}
|
||||
|
||||
var start = start.line, has = hasImport(start), prev;
|
||||
if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
|
||||
var startLine = start.line, has = hasImport(startLine), prev;
|
||||
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
|
||||
return null;
|
||||
for (var end = has.end;;) {
|
||||
var next = hasImport(end.line + 1);
|
||||
if (next == null) break;
|
||||
end = next.end;
|
||||
}
|
||||
return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
|
||||
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
||||
@ -91,14 +91,14 @@ CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
||||
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
||||
}
|
||||
|
||||
var start = start.line, has = hasInclude(start);
|
||||
if (has == null || hasInclude(start - 1) != null) return null;
|
||||
for (var end = start;;) {
|
||||
var startLine = start.line, has = hasInclude(startLine);
|
||||
if (has == null || hasInclude(startLine - 1) != null) return null;
|
||||
for (var end = startLine;;) {
|
||||
var next = hasInclude(end + 1);
|
||||
if (next == null) break;
|
||||
++end;
|
||||
}
|
||||
return {from: CodeMirror.Pos(start, has + 1),
|
||||
return {from: CodeMirror.Pos(startLine, has + 1),
|
||||
to: cm.clipPos(CodeMirror.Pos(end))};
|
||||
});
|
||||
|
||||
|
@ -29,7 +29,7 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
|
||||
}
|
||||
if (pass == 1 && found < start.ch) return;
|
||||
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
|
||||
(lineText.slice(found - endToken.length, found) == endToken ||
|
||||
(found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
|
||||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
|
||||
startCh = found + startToken.length;
|
||||
break;
|
||||
|
@ -49,7 +49,7 @@
|
||||
});
|
||||
var myRange = cm.markText(range.from, range.to, {
|
||||
replacedWith: myWidget,
|
||||
clearOnEnter: true,
|
||||
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
||||
__isFold: true
|
||||
});
|
||||
myRange.on("clear", function(from, to) {
|
||||
@ -129,7 +129,8 @@
|
||||
rangeFinder: CodeMirror.fold.auto,
|
||||
widget: "\u2194",
|
||||
minFoldSize: 0,
|
||||
scanUp: false
|
||||
scanUp: false,
|
||||
clearOnEnter: true
|
||||
};
|
||||
|
||||
CodeMirror.defineOption("foldOptions", null);
|
||||
|
@ -50,7 +50,7 @@
|
||||
}
|
||||
|
||||
function isFolded(cm, line) {
|
||||
var marks = cm.findMarksAt(Pos(line));
|
||||
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
||||
for (var i = 0; i < marks.length; ++i)
|
||||
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
||||
}
|
||||
|
@ -11,32 +11,36 @@
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
function lineIndent(cm, lineNo) {
|
||||
var text = cm.getLine(lineNo)
|
||||
var spaceTo = text.search(/\S/)
|
||||
if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))
|
||||
return -1
|
||||
return CodeMirror.countColumn(text, null, cm.getOption("tabSize"))
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
|
||||
var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line);
|
||||
if (!/\S/.test(firstLine)) return;
|
||||
var getIndent = function(line) {
|
||||
return CodeMirror.countColumn(line, null, tabSize);
|
||||
};
|
||||
var myIndent = getIndent(firstLine);
|
||||
var lastLineInFold = null;
|
||||
var myIndent = lineIndent(cm, start.line)
|
||||
if (myIndent < 0) return
|
||||
var lastLineInFold = null
|
||||
|
||||
// Go through lines until we find a line that definitely doesn't belong in
|
||||
// the block we're folding, or to the end.
|
||||
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
|
||||
var curLine = cm.getLine(i);
|
||||
var curIndent = getIndent(curLine);
|
||||
if (curIndent > myIndent) {
|
||||
var indent = lineIndent(cm, i)
|
||||
if (indent == -1) {
|
||||
} else if (indent > myIndent) {
|
||||
// Lines with a greater indent are considered part of the block.
|
||||
lastLineInFold = i;
|
||||
} else if (!/\S/.test(curLine)) {
|
||||
// Empty lines might be breaks within the block we're trying to fold.
|
||||
} else {
|
||||
// A non-empty line at an indent equal to or less than ours marks the
|
||||
// start of another block.
|
||||
// If this line has non-space, non-comment content, and is
|
||||
// indented less or equal to the start line, it is the start of
|
||||
// another block.
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastLineInFold) return {
|
||||
from: CodeMirror.Pos(start.line, firstLine.length),
|
||||
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
|
||||
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
|
||||
};
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
function Iter(cm, line, ch, range) {
|
||||
this.line = line; this.ch = ch;
|
||||
this.cm = cm; this.text = cm.getLine(line);
|
||||
this.min = range ? range.from : cm.firstLine();
|
||||
this.max = range ? range.to - 1 : cm.lastLine();
|
||||
this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
|
||||
this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
|
||||
}
|
||||
|
||||
function tagAt(iter, ch) {
|
||||
@ -140,9 +140,9 @@
|
||||
var openTag = toNextTag(iter), end;
|
||||
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
|
||||
if (!openTag[1] && end != "selfClose") {
|
||||
var start = Pos(iter.line, iter.ch);
|
||||
var close = findMatchingClose(iter, openTag[2]);
|
||||
return close && {from: start, to: close.from};
|
||||
var startPos = Pos(iter.line, iter.ch);
|
||||
var endPos = findMatchingClose(iter, openTag[2]);
|
||||
return endPos && {from: startPos, to: endPos.from};
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -163,10 +163,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.findEnclosingTag = function(cm, pos, range) {
|
||||
CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {
|
||||
var iter = new Iter(cm, pos.line, pos.ch, range);
|
||||
for (;;) {
|
||||
var open = findMatchingOpen(iter);
|
||||
var open = findMatchingOpen(iter, tag);
|
||||
if (!open) break;
|
||||
var forward = new Iter(cm, pos.line, pos.ch, range);
|
||||
var close = findMatchingClose(forward, open.tag);
|
||||
|
@ -97,6 +97,15 @@
|
||||
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
||||
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
||||
|
||||
function forAllProps(obj, callback) {
|
||||
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||
for (var name in obj) callback(name)
|
||||
} else {
|
||||
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
||||
Object.getOwnPropertyNames(o).forEach(callback)
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletions(token, context, keywords, options) {
|
||||
var found = [], start = token.string, global = options && options.globalScope || window;
|
||||
function maybeAdd(str) {
|
||||
@ -106,7 +115,7 @@
|
||||
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
||||
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
||||
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
||||
for (var name in obj) maybeAdd(name);
|
||||
forAllProps(obj, maybeAdd)
|
||||
}
|
||||
|
||||
if (context && context.length) {
|
||||
|
@ -25,8 +25,6 @@
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
max-width: 19em;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
|
@ -229,6 +229,9 @@
|
||||
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
|
||||
(completion.options.container || document.body).appendChild(hints);
|
||||
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
|
||||
var scrolls = hints.scrollHeight > hints.clientHeight + 1
|
||||
var startScroll = cm.getScrollInfo();
|
||||
|
||||
if (overlapY > 0) {
|
||||
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
|
||||
if (curTop - height > 0) { // Fits above cursor
|
||||
@ -253,6 +256,8 @@
|
||||
}
|
||||
hints.style.left = (left = pos.left - overlapX) + "px";
|
||||
}
|
||||
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
|
||||
node.style.paddingRight = cm.display.nativeBarWidth + "px"
|
||||
|
||||
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
|
||||
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
|
||||
@ -270,7 +275,6 @@
|
||||
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
|
||||
}
|
||||
|
||||
var startScroll = cm.getScrollInfo();
|
||||
cm.on("scroll", this.onScroll = function() {
|
||||
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
|
||||
var newTop = top + startScroll.top - curScroll.top;
|
||||
|
@ -14,11 +14,12 @@
|
||||
var tables;
|
||||
var defaultTable;
|
||||
var keywords;
|
||||
var identifierQuote;
|
||||
var CONS = {
|
||||
QUERY_DIV: ";",
|
||||
ALIAS_KEYWORD: "AS"
|
||||
};
|
||||
var Pos = CodeMirror.Pos;
|
||||
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
|
||||
|
||||
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
|
||||
|
||||
@ -28,6 +29,12 @@
|
||||
return CodeMirror.resolveMode(mode).keywords;
|
||||
}
|
||||
|
||||
function getIdentifierQuote(editor) {
|
||||
var mode = editor.doc.modeOption;
|
||||
if (mode === "sql") mode = "text/x-sql";
|
||||
return CodeMirror.resolveMode(mode).identifierQuote || "`";
|
||||
}
|
||||
|
||||
function getText(item) {
|
||||
return typeof item == "string" ? item : item.text;
|
||||
}
|
||||
@ -86,17 +93,25 @@
|
||||
}
|
||||
|
||||
function cleanName(name) {
|
||||
// Get rid name from backticks(`) and preceding dot(.)
|
||||
// Get rid name from identifierQuote and preceding dot(.)
|
||||
if (name.charAt(0) == ".") {
|
||||
name = name.substr(1);
|
||||
}
|
||||
return name.replace(/`/g, "");
|
||||
// replace doublicated identifierQuotes with single identifierQuotes
|
||||
// and remove single identifierQuotes
|
||||
var nameParts = name.split(identifierQuote+identifierQuote);
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
|
||||
return nameParts.join(identifierQuote);
|
||||
}
|
||||
|
||||
function insertBackticks(name) {
|
||||
function insertIdentifierQuotes(name) {
|
||||
var nameParts = getText(name).split(".");
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = "`" + nameParts[i] + "`";
|
||||
nameParts[i] = identifierQuote +
|
||||
// doublicate identifierQuotes
|
||||
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
|
||||
identifierQuote;
|
||||
var escaped = nameParts.join(".");
|
||||
if (typeof name == "string") return escaped;
|
||||
name = shallowClone(name);
|
||||
@ -106,13 +121,13 @@
|
||||
|
||||
function nameCompletion(cur, token, result, editor) {
|
||||
// Try to complete table, column names and return start position of completion
|
||||
var useBacktick = false;
|
||||
var useIdentifierQuotes = false;
|
||||
var nameParts = [];
|
||||
var start = token.start;
|
||||
var cont = true;
|
||||
while (cont) {
|
||||
cont = (token.string.charAt(0) == ".");
|
||||
useBacktick = useBacktick || (token.string.charAt(0) == "`");
|
||||
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
|
||||
|
||||
start = token.start;
|
||||
nameParts.unshift(cleanName(token.string));
|
||||
@ -127,12 +142,12 @@
|
||||
// Try to complete table names
|
||||
var string = nameParts.join(".");
|
||||
addMatches(result, string, tables, function(w) {
|
||||
return useBacktick ? insertBackticks(w) : w;
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns from defaultTable
|
||||
addMatches(result, string, defaultTable, function(w) {
|
||||
return useBacktick ? insertBackticks(w) : w;
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns
|
||||
@ -162,7 +177,7 @@
|
||||
w = shallowClone(w);
|
||||
w.text = tableInsert + "." + w.text;
|
||||
}
|
||||
return useBacktick ? insertBackticks(w) : w;
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
}
|
||||
|
||||
@ -170,21 +185,9 @@
|
||||
}
|
||||
|
||||
function eachWord(lineText, f) {
|
||||
if (!lineText) return;
|
||||
var excepted = /[,;]/g;
|
||||
var words = lineText.split(" ");
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
f(words[i]?words[i].replace(excepted, '') : '');
|
||||
}
|
||||
}
|
||||
|
||||
function convertCurToNumber(cur) {
|
||||
// max characters of a line is 999,999.
|
||||
return cur.line + cur.ch / Math.pow(10, 6);
|
||||
}
|
||||
|
||||
function convertNumberToCur(num) {
|
||||
return Pos(Math.floor(num), +num.toString().split('.').pop());
|
||||
var words = lineText.split(/\s+/)
|
||||
for (var i = 0; i < words.length; i++)
|
||||
if (words[i]) f(words[i].replace(/[,;]/g, ''))
|
||||
}
|
||||
|
||||
function findTableByAlias(alias, editor) {
|
||||
@ -209,15 +212,14 @@
|
||||
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
|
||||
|
||||
//find valid range
|
||||
var prevItem = 0;
|
||||
var current = convertCurToNumber(editor.getCursor());
|
||||
var prevItem = null;
|
||||
var current = editor.getCursor()
|
||||
for (var i = 0; i < separator.length; i++) {
|
||||
var _v = convertCurToNumber(separator[i]);
|
||||
if (current > prevItem && current <= _v) {
|
||||
validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
|
||||
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
|
||||
validRange = {start: prevItem, end: separator[i]};
|
||||
break;
|
||||
}
|
||||
prevItem = _v;
|
||||
prevItem = separator[i];
|
||||
}
|
||||
|
||||
var query = doc.getRange(validRange.start, validRange.end, false);
|
||||
@ -241,7 +243,8 @@
|
||||
var defaultTableName = options && options.defaultTable;
|
||||
var disableKeywords = options && options.disableKeywords;
|
||||
defaultTable = defaultTableName && getTable(defaultTableName);
|
||||
keywords = keywords || getKeywords(editor);
|
||||
keywords = getKeywords(editor);
|
||||
identifierQuote = getIdentifierQuote(editor);
|
||||
|
||||
if (defaultTableName && !defaultTable)
|
||||
defaultTable = findTableByAlias(defaultTableName, editor);
|
||||
@ -259,7 +262,7 @@
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
|
||||
if (token.string.match(/^[.`\w@]\w*$/)) {
|
||||
if (token.string.match(/^[.`"\w@]\w*$/)) {
|
||||
search = token.string;
|
||||
start = token.start;
|
||||
end = token.end;
|
||||
@ -267,7 +270,7 @@
|
||||
start = end = cur.ch;
|
||||
search = "";
|
||||
}
|
||||
if (search.charAt(0) == "." || search.charAt(0) == "`") {
|
||||
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
|
||||
start = nameCompletion(cur, token, result, editor);
|
||||
} else {
|
||||
addMatches(result, search, tables, function(w) {return w;});
|
||||
|
@ -4,10 +4,10 @@
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: infobackground;
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: infotext;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
|
@ -140,7 +140,12 @@
|
||||
if (options.async || getAnnotations.async) {
|
||||
lintAsync(cm, getAnnotations, passOptions)
|
||||
} else {
|
||||
updateLinting(cm, getAnnotations(cm.getValue(), passOptions, cm));
|
||||
var annotations = getAnnotations(cm.getValue(), passOptions, cm);
|
||||
if (!annotations) return;
|
||||
if (annotations.then) annotations.then(function(issues) {
|
||||
updateLinting(cm, issues);
|
||||
});
|
||||
else updateLinting(cm, annotations);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,7 +231,7 @@
|
||||
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
|
||||
if (state.options.lintOnChange !== false)
|
||||
cm.on("change", onChange);
|
||||
if (state.options.tooltips != false)
|
||||
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||
|
||||
startLinting(cm);
|
||||
|
@ -19,8 +19,15 @@ CodeMirror.registerHelper("lint", "yaml", function(text) {
|
||||
var found = [];
|
||||
try { jsyaml.load(text); }
|
||||
catch(e) {
|
||||
var loc = e.mark;
|
||||
found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message });
|
||||
var loc = e.mark,
|
||||
// js-yaml YAMLException doesn't always provide an accurate lineno
|
||||
// e.g., when there are multiple yaml docs
|
||||
// ---
|
||||
// ---
|
||||
// foo:bar
|
||||
from = loc ? CodeMirror.Pos(loc.line, loc.column) : CodeMirror.Pos(0, 0),
|
||||
to = from;
|
||||
found.push({ from: from, to: to, message: e.message });
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
@ -37,18 +37,29 @@
|
||||
constructor: DiffView,
|
||||
init: function(pane, orig, options) {
|
||||
this.edit = this.mv.edit;
|
||||
(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
|
||||
;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
|
||||
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
|
||||
this.orig.state.diffViews = [this];
|
||||
if (this.mv.options.connect == "align") {
|
||||
if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit)
|
||||
this.orig.state.trackAlignable = new TrackAlignable(this.orig)
|
||||
}
|
||||
|
||||
this.diff = getDiff(asString(orig), asString(options.value));
|
||||
this.orig.state.diffViews = [this];
|
||||
var classLocation = options.chunkClassLocation || "background";
|
||||
if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation]
|
||||
this.classes.classLocation = classLocation
|
||||
|
||||
this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace);
|
||||
this.chunks = getChunks(this.diff);
|
||||
this.diffOutOfDate = this.dealigned = false;
|
||||
this.needsScrollSync = null
|
||||
|
||||
this.showDifferences = options.showDifferences !== false;
|
||||
},
|
||||
registerEvents: function(otherDv) {
|
||||
this.forceUpdate = registerUpdate(this);
|
||||
setScrollLock(this, true, false);
|
||||
registerScroll(this);
|
||||
registerScroll(this, otherDv);
|
||||
},
|
||||
setShowDifferences: function(val) {
|
||||
val = val !== false;
|
||||
@ -61,7 +72,7 @@
|
||||
|
||||
function ensureDiff(dv) {
|
||||
if (dv.diffOutOfDate) {
|
||||
dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
|
||||
dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace);
|
||||
dv.chunks = getChunks(dv.diff);
|
||||
dv.diffOutOfDate = false;
|
||||
CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
|
||||
@ -88,10 +99,12 @@
|
||||
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
|
||||
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
|
||||
}
|
||||
makeConnections(dv);
|
||||
|
||||
if (dv.mv.options.connect == "align")
|
||||
alignChunks(dv);
|
||||
makeConnections(dv);
|
||||
if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync)
|
||||
|
||||
updating = false;
|
||||
}
|
||||
function setDealign(fast) {
|
||||
@ -113,37 +126,49 @@
|
||||
// Update faster when a line was added/removed
|
||||
setDealign(change.text.length - 1 != change.to.line - change.from.line);
|
||||
}
|
||||
function swapDoc() {
|
||||
dv.diffOutOfDate = true;
|
||||
dv.dealigned = true;
|
||||
update("full");
|
||||
}
|
||||
dv.edit.on("change", change);
|
||||
dv.orig.on("change", change);
|
||||
dv.edit.on("markerAdded", setDealign);
|
||||
dv.edit.on("markerCleared", setDealign);
|
||||
dv.orig.on("markerAdded", setDealign);
|
||||
dv.orig.on("markerCleared", setDealign);
|
||||
dv.edit.on("swapDoc", swapDoc);
|
||||
dv.orig.on("swapDoc", swapDoc);
|
||||
if (dv.mv.options.connect == "align") {
|
||||
CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign)
|
||||
CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign)
|
||||
}
|
||||
dv.edit.on("viewportChange", function() { set(false); });
|
||||
dv.orig.on("viewportChange", function() { set(false); });
|
||||
update();
|
||||
return update;
|
||||
}
|
||||
|
||||
function registerScroll(dv) {
|
||||
function registerScroll(dv, otherDv) {
|
||||
dv.edit.on("scroll", function() {
|
||||
syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
|
||||
syncScroll(dv, true) && makeConnections(dv);
|
||||
});
|
||||
dv.orig.on("scroll", function() {
|
||||
syncScroll(dv, DIFF_DELETE) && makeConnections(dv);
|
||||
syncScroll(dv, false) && makeConnections(dv);
|
||||
if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv);
|
||||
});
|
||||
}
|
||||
|
||||
function syncScroll(dv, type) {
|
||||
function syncScroll(dv, toOrig) {
|
||||
// Change handler will do a refresh after a timeout when diff is out of date
|
||||
if (dv.diffOutOfDate) return false;
|
||||
if (dv.diffOutOfDate) {
|
||||
if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig
|
||||
return false
|
||||
}
|
||||
dv.needsScrollSync = null
|
||||
if (!dv.lockScroll) return true;
|
||||
var editor, other, now = +new Date;
|
||||
if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; }
|
||||
if (toOrig) { editor = dv.edit; other = dv.orig; }
|
||||
else { editor = dv.orig; other = dv.edit; }
|
||||
// Don't take action if the position of this editor was recently set
|
||||
// (to prevent feedback loops)
|
||||
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false;
|
||||
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false;
|
||||
|
||||
var sInfo = editor.getScrollInfo();
|
||||
if (dv.mv.options.connect == "align") {
|
||||
@ -151,9 +176,9 @@
|
||||
} else {
|
||||
var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
|
||||
var mid = editor.lineAtHeight(midY, "local");
|
||||
var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT);
|
||||
var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
|
||||
var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
|
||||
var around = chunkBoundariesAround(dv.chunks, mid, toOrig);
|
||||
var off = getOffsets(editor, toOrig ? around.edit : around.orig);
|
||||
var offOther = getOffsets(other, toOrig ? around.orig : around.edit);
|
||||
var ratio = (midY - off.top) / (off.bot - off.top);
|
||||
var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
|
||||
|
||||
@ -191,16 +216,22 @@
|
||||
|
||||
// Updating the marks for editor content
|
||||
|
||||
function removeClass(editor, line, classes) {
|
||||
var locs = classes.classLocation
|
||||
for (var i = 0; i < locs.length; i++) {
|
||||
editor.removeLineClass(line, locs[i], classes.chunk);
|
||||
editor.removeLineClass(line, locs[i], classes.start);
|
||||
editor.removeLineClass(line, locs[i], classes.end);
|
||||
}
|
||||
}
|
||||
|
||||
function clearMarks(editor, arr, classes) {
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
var mark = arr[i];
|
||||
if (mark instanceof CodeMirror.TextMarker) {
|
||||
if (mark instanceof CodeMirror.TextMarker)
|
||||
mark.clear();
|
||||
} else if (mark.parent) {
|
||||
editor.removeLineClass(mark, "background", classes.chunk);
|
||||
editor.removeLineClass(mark, "background", classes.start);
|
||||
editor.removeLineClass(mark, "background", classes.end);
|
||||
}
|
||||
else if (mark.parent)
|
||||
removeClass(editor, mark, classes);
|
||||
}
|
||||
arr.length = 0;
|
||||
}
|
||||
@ -226,28 +257,34 @@
|
||||
});
|
||||
}
|
||||
|
||||
function addClass(editor, lineNr, classes, main, start, end) {
|
||||
var locs = classes.classLocation, line = editor.getLineHandle(lineNr);
|
||||
for (var i = 0; i < locs.length; i++) {
|
||||
if (main) editor.addLineClass(line, locs[i], classes.chunk);
|
||||
if (start) editor.addLineClass(line, locs[i], classes.start);
|
||||
if (end) editor.addLineClass(line, locs[i], classes.end);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function markChanges(editor, diff, type, marks, from, to, classes) {
|
||||
var pos = Pos(0, 0);
|
||||
var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
|
||||
var cls = type == DIFF_DELETE ? classes.del : classes.insert;
|
||||
function markChunk(start, end) {
|
||||
var bfrom = Math.max(from, start), bto = Math.min(to, end);
|
||||
for (var i = bfrom; i < bto; ++i) {
|
||||
var line = editor.addLineClass(i, "background", classes.chunk);
|
||||
if (i == start) editor.addLineClass(line, "background", classes.start);
|
||||
if (i == end - 1) editor.addLineClass(line, "background", classes.end);
|
||||
marks.push(line);
|
||||
}
|
||||
for (var i = bfrom; i < bto; ++i)
|
||||
marks.push(addClass(editor, i, classes, true, i == start, i == end - 1));
|
||||
// When the chunk is empty, make sure a horizontal line shows up
|
||||
if (start == end && bfrom == end && bto == end) {
|
||||
if (bfrom)
|
||||
marks.push(editor.addLineClass(bfrom - 1, "background", classes.end));
|
||||
marks.push(addClass(editor, bfrom - 1, classes, false, false, true));
|
||||
else
|
||||
marks.push(editor.addLineClass(bfrom, "background", classes.start));
|
||||
marks.push(addClass(editor, bfrom, classes, false, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
var chunkStart = 0;
|
||||
var chunkStart = 0, pending = false;
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i], tp = part[0], str = part[1];
|
||||
if (tp == DIFF_EQUAL) {
|
||||
@ -255,10 +292,11 @@
|
||||
moveOver(pos, str);
|
||||
var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
|
||||
if (cleanTo > cleanFrom) {
|
||||
if (i) markChunk(chunkStart, cleanFrom);
|
||||
if (pending) { markChunk(chunkStart, cleanFrom); pending = false }
|
||||
chunkStart = cleanTo;
|
||||
}
|
||||
} else {
|
||||
pending = true
|
||||
if (tp == type) {
|
||||
var end = moveOver(pos, str, true);
|
||||
var a = posMax(top, pos), b = posMin(bot, end);
|
||||
@ -268,7 +306,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1);
|
||||
if (pending) markChunk(chunkStart, pos.line + 1);
|
||||
}
|
||||
|
||||
// Updating the gap between editor and original
|
||||
@ -284,7 +322,9 @@
|
||||
if (dv.copyButtons) clear(dv.copyButtons);
|
||||
|
||||
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
|
||||
var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
|
||||
var outerTop = dv.mv.wrap.getBoundingClientRect().top
|
||||
var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
|
||||
var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
|
||||
for (var i = 0; i < dv.chunks.length; i++) {
|
||||
var ch = dv.chunks[i];
|
||||
if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
|
||||
@ -305,29 +345,81 @@
|
||||
return origStart + (editLine - editStart);
|
||||
}
|
||||
|
||||
function findAlignedLines(dv, other) {
|
||||
var linesToAlign = [];
|
||||
for (var i = 0; i < dv.chunks.length; i++) {
|
||||
var chunk = dv.chunks[i];
|
||||
linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]);
|
||||
// Combines information about chunks and widgets/markers to return
|
||||
// an array of lines, in a single editor, that probably need to be
|
||||
// aligned with their counterparts in the editor next to it.
|
||||
function alignableFor(cm, chunks, isOrig) {
|
||||
var tracker = cm.state.trackAlignable
|
||||
var start = cm.firstLine(), trackI = 0
|
||||
var result = []
|
||||
for (var i = 0;; i++) {
|
||||
var chunk = chunks[i]
|
||||
var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom
|
||||
for (; trackI < tracker.alignable.length; trackI += 2) {
|
||||
var n = tracker.alignable[trackI] + 1
|
||||
if (n <= start) continue
|
||||
if (n <= chunkStart) result.push(n)
|
||||
else break
|
||||
}
|
||||
if (!chunk) break
|
||||
result.push(start = isOrig ? chunk.origTo : chunk.editTo)
|
||||
}
|
||||
if (other) {
|
||||
for (var i = 0; i < other.chunks.length; i++) {
|
||||
var chunk = other.chunks[i];
|
||||
for (var j = 0; j < linesToAlign.length; j++) {
|
||||
var align = linesToAlign[j];
|
||||
if (align[1] == chunk.editTo) {
|
||||
j = -1;
|
||||
break;
|
||||
} else if (align[1] > chunk.editTo) {
|
||||
break;
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Given information about alignable lines in two editors, fill in
|
||||
// the result (an array of three-element arrays) to reflect the
|
||||
// lines that need to be aligned with each other.
|
||||
function mergeAlignable(result, origAlignable, chunks, setIndex) {
|
||||
var rI = 0, origI = 0, chunkI = 0, diff = 0
|
||||
outer: for (;; rI++) {
|
||||
var nextR = result[rI], nextO = origAlignable[origI]
|
||||
if (!nextR && nextO == null) break
|
||||
|
||||
var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO
|
||||
while (chunkI < chunks.length) {
|
||||
var chunk = chunks[chunkI]
|
||||
if (chunk.origFrom <= oLine && chunk.origTo > oLine) {
|
||||
origI++
|
||||
rI--
|
||||
continue outer;
|
||||
}
|
||||
if (j > -1)
|
||||
linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]);
|
||||
if (chunk.editTo > rLine) {
|
||||
if (chunk.editFrom <= rLine) continue outer;
|
||||
break
|
||||
}
|
||||
diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom)
|
||||
chunkI++
|
||||
}
|
||||
if (rLine == oLine - diff) {
|
||||
nextR[setIndex] = oLine
|
||||
origI++
|
||||
} else if (rLine < oLine - diff) {
|
||||
nextR[setIndex] = rLine + diff
|
||||
} else {
|
||||
var record = [oLine - diff, null, null]
|
||||
record[setIndex] = oLine
|
||||
result.splice(rI, 0, record)
|
||||
origI++
|
||||
}
|
||||
}
|
||||
return linesToAlign;
|
||||
}
|
||||
|
||||
function findAlignedLines(dv, other) {
|
||||
var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
|
||||
if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
|
||||
var n = other.chunks[i].editTo
|
||||
while (j < alignable.length && alignable[j] < n) j++
|
||||
if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n)
|
||||
}
|
||||
for (var i = 0; i < alignable.length; i++)
|
||||
result.push([alignable[i], null, null])
|
||||
|
||||
mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1)
|
||||
if (other)
|
||||
mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function alignChunks(dv, force) {
|
||||
@ -350,7 +442,7 @@
|
||||
aligners[i].clear();
|
||||
aligners.length = 0;
|
||||
|
||||
var cm = [dv.orig, dv.edit], scroll = [];
|
||||
var cm = [dv.edit, dv.orig], scroll = [];
|
||||
if (other) cm.push(other.orig);
|
||||
for (var i = 0; i < cm.length; i++)
|
||||
scroll.push(cm[i].getScrollInfo().top);
|
||||
@ -385,18 +477,18 @@
|
||||
var elt = document.createElement("div");
|
||||
elt.className = "CodeMirror-merge-spacer";
|
||||
elt.style.height = size + "px"; elt.style.minWidth = "1px";
|
||||
return cm.addLineWidget(line, elt, {height: size, above: above});
|
||||
return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true});
|
||||
}
|
||||
|
||||
function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
|
||||
var flip = dv.type == "left";
|
||||
var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig;
|
||||
var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig;
|
||||
if (dv.svg) {
|
||||
var topLpx = top;
|
||||
var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
|
||||
var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit;
|
||||
if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
|
||||
var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig;
|
||||
var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit;
|
||||
var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig;
|
||||
var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit;
|
||||
if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
|
||||
var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
|
||||
var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
|
||||
@ -410,10 +502,10 @@
|
||||
var editOriginals = dv.mv.options.allowEditingOriginals;
|
||||
copy.title = editOriginals ? "Push to left" : "Revert chunk";
|
||||
copy.chunk = chunk;
|
||||
copy.style.top = top + "px";
|
||||
copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px";
|
||||
|
||||
if (editOriginals) {
|
||||
var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit;
|
||||
var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
|
||||
var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
|
||||
"CodeMirror-merge-copy-reverse"));
|
||||
copyReverse.title = "Push to right";
|
||||
@ -427,9 +519,15 @@
|
||||
|
||||
function copyChunk(dv, to, from, chunk) {
|
||||
if (dv.diffOutOfDate) return;
|
||||
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
|
||||
var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
|
||||
to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0))
|
||||
var origEnd = Pos(chunk.origTo, 0)
|
||||
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
|
||||
var editEnd = Pos(chunk.editTo, 0)
|
||||
var handler = dv.mv.options.revertChunk
|
||||
if (handler)
|
||||
handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd)
|
||||
else
|
||||
to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd)
|
||||
}
|
||||
|
||||
// Merge view, containing 0, 1, or 2 diff views.
|
||||
@ -447,18 +545,18 @@
|
||||
|
||||
if (hasLeft) {
|
||||
left = this.left = new DiffView(this, "left");
|
||||
var leftPane = elt("div", null, "CodeMirror-merge-pane");
|
||||
var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left");
|
||||
wrap.push(leftPane);
|
||||
wrap.push(buildGap(left));
|
||||
}
|
||||
|
||||
var editPane = elt("div", null, "CodeMirror-merge-pane");
|
||||
var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor");
|
||||
wrap.push(editPane);
|
||||
|
||||
if (hasRight) {
|
||||
right = this.right = new DiffView(this, "right");
|
||||
wrap.push(buildGap(right));
|
||||
var rightPane = elt("div", null, "CodeMirror-merge-pane");
|
||||
var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right");
|
||||
wrap.push(rightPane);
|
||||
}
|
||||
|
||||
@ -471,7 +569,6 @@
|
||||
|
||||
if (left) left.init(leftPane, origLeft, options);
|
||||
if (right) right.init(rightPane, origRight, options);
|
||||
|
||||
if (options.collapseIdentical)
|
||||
this.editor().operation(function() {
|
||||
collapseIdenticalStretches(self, options.collapseIdentical);
|
||||
@ -480,6 +577,9 @@
|
||||
this.aligners = [];
|
||||
alignChunks(this.left || this.right, true);
|
||||
}
|
||||
if (left) left.registerEvents(right)
|
||||
if (right) right.registerEvents(left)
|
||||
|
||||
|
||||
var onResize = function() {
|
||||
if (left) makeConnections(left);
|
||||
@ -522,7 +622,7 @@
|
||||
}
|
||||
|
||||
MergeView.prototype = {
|
||||
constuctor: MergeView,
|
||||
constructor: MergeView,
|
||||
editor: function() { return this.edit; },
|
||||
rightOriginal: function() { return this.right && this.right.orig; },
|
||||
leftOriginal: function() { return this.left && this.left.orig; },
|
||||
@ -546,13 +646,12 @@
|
||||
// Operations on diffs
|
||||
|
||||
var dmp = new diff_match_patch();
|
||||
function getDiff(a, b) {
|
||||
function getDiff(a, b, ignoreWhitespace) {
|
||||
var diff = dmp.diff_main(a, b);
|
||||
dmp.diff_cleanupSemantic(diff);
|
||||
// The library sometimes leaves in empty parts, which confuse the algorithm
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i];
|
||||
if (!part[1]) {
|
||||
if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
|
||||
diff.splice(i--, 1);
|
||||
} else if (i && diff[i - 1][0] == part[0]) {
|
||||
diff.splice(i--, 1);
|
||||
@ -569,7 +668,7 @@
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i], tp = part[0];
|
||||
if (tp == DIFF_EQUAL) {
|
||||
var startOff = startOfLineClean(diff, i) ? 0 : 1;
|
||||
var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
|
||||
var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
|
||||
moveOver(edit, part[1], null, orig);
|
||||
var endOff = endOfLineClean(diff, i) ? 1 : 0;
|
||||
@ -592,10 +691,10 @@
|
||||
function endOfLineClean(diff, i) {
|
||||
if (i == diff.length - 1) return true;
|
||||
var next = diff[i + 1][1];
|
||||
if (next.length == 1 || next.charCodeAt(0) != 10) return false;
|
||||
if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
|
||||
if (i == diff.length - 2) return true;
|
||||
next = diff[i + 2][1];
|
||||
return next.length > 1 && next.charCodeAt(0) == 10;
|
||||
return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
|
||||
}
|
||||
|
||||
function startOfLineClean(diff, i) {
|
||||
@ -729,6 +828,131 @@
|
||||
return out;
|
||||
}
|
||||
|
||||
// Tracks collapsed markers and line widgets, in order to be able to
|
||||
// accurately align the content of two editors.
|
||||
|
||||
var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
|
||||
|
||||
function TrackAlignable(cm) {
|
||||
this.cm = cm
|
||||
this.alignable = []
|
||||
this.height = cm.doc.height
|
||||
var self = this
|
||||
cm.on("markerAdded", function(_, marker) {
|
||||
if (!marker.collapsed) return
|
||||
var found = marker.find(1)
|
||||
if (found != null) self.set(found.line, F_MARKER)
|
||||
})
|
||||
cm.on("markerCleared", function(_, marker, _min, max) {
|
||||
if (max != null && marker.collapsed)
|
||||
self.check(max, F_MARKER, self.hasMarker)
|
||||
})
|
||||
cm.on("markerChanged", this.signal.bind(this))
|
||||
cm.on("lineWidgetAdded", function(_, widget, lineNo) {
|
||||
if (widget.mergeSpacer) return
|
||||
if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
|
||||
else self.set(lineNo, F_WIDGET)
|
||||
})
|
||||
cm.on("lineWidgetCleared", function(_, widget, lineNo) {
|
||||
if (widget.mergeSpacer) return
|
||||
if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
|
||||
else self.check(lineNo, F_WIDGET, self.hasWidget)
|
||||
})
|
||||
cm.on("lineWidgetChanged", this.signal.bind(this))
|
||||
cm.on("change", function(_, change) {
|
||||
var start = change.from.line, nBefore = change.to.line - change.from.line
|
||||
var nAfter = change.text.length - 1, end = start + nAfter
|
||||
if (nBefore || nAfter) self.map(start, nBefore, nAfter)
|
||||
self.check(end, F_MARKER, self.hasMarker)
|
||||
if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
|
||||
})
|
||||
cm.on("viewportChange", function() {
|
||||
if (self.cm.doc.height != self.height) self.signal()
|
||||
})
|
||||
}
|
||||
|
||||
TrackAlignable.prototype = {
|
||||
signal: function() {
|
||||
CodeMirror.signal(this, "realign")
|
||||
this.height = this.cm.doc.height
|
||||
},
|
||||
|
||||
set: function(n, flags) {
|
||||
var pos = -1
|
||||
for (; pos < this.alignable.length; pos += 2) {
|
||||
var diff = this.alignable[pos] - n
|
||||
if (diff == 0) {
|
||||
if ((this.alignable[pos + 1] & flags) == flags) return
|
||||
this.alignable[pos + 1] |= flags
|
||||
this.signal()
|
||||
return
|
||||
}
|
||||
if (diff > 0) break
|
||||
}
|
||||
this.signal()
|
||||
this.alignable.splice(pos, 0, n, flags)
|
||||
},
|
||||
|
||||
find: function(n) {
|
||||
for (var i = 0; i < this.alignable.length; i += 2)
|
||||
if (this.alignable[i] == n) return i
|
||||
return -1
|
||||
},
|
||||
|
||||
check: function(n, flag, pred) {
|
||||
var found = this.find(n)
|
||||
if (found == -1 || !(this.alignable[found + 1] & flag)) return
|
||||
if (!pred.call(this, n)) {
|
||||
this.signal()
|
||||
var flags = this.alignable[found + 1] & ~flag
|
||||
if (flags) this.alignable[found + 1] = flags
|
||||
else this.alignable.splice(found, 2)
|
||||
}
|
||||
},
|
||||
|
||||
hasMarker: function(n) {
|
||||
var handle = this.cm.getLineHandle(n)
|
||||
if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
|
||||
if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null)
|
||||
return true
|
||||
return false
|
||||
},
|
||||
|
||||
hasWidget: function(n) {
|
||||
var handle = this.cm.getLineHandle(n)
|
||||
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
|
||||
if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
|
||||
return false
|
||||
},
|
||||
|
||||
hasWidgetBelow: function(n) {
|
||||
if (n == this.cm.lastLine()) return false
|
||||
var handle = this.cm.getLineHandle(n + 1)
|
||||
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
|
||||
if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
|
||||
return false
|
||||
},
|
||||
|
||||
map: function(from, nBefore, nAfter) {
|
||||
var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
|
||||
for (var i = 0; i < this.alignable.length; i += 2) {
|
||||
var n = this.alignable[i]
|
||||
if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
|
||||
if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
|
||||
if (n <= from) continue
|
||||
else if (n < to) this.alignable.splice(i--, 2)
|
||||
else this.alignable[i] += diff
|
||||
}
|
||||
if (widgetFrom > -1) {
|
||||
var flags = this.alignable[widgetFrom + 1]
|
||||
if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
|
||||
else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
|
||||
}
|
||||
if (widgetTo > -1 && nAfter)
|
||||
this.set(from + nAfter, F_WIDGET_BELOW)
|
||||
}
|
||||
}
|
||||
|
||||
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
|
||||
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
|
||||
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
|
||||
|
@ -76,8 +76,13 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
|
||||
innerMode: function(state) { return {state: state.base, mode: base}; },
|
||||
|
||||
blankLine: function(state) {
|
||||
if (base.blankLine) base.blankLine(state.base);
|
||||
if (overlay.blankLine) overlay.blankLine(state.overlay);
|
||||
var baseToken, overlayToken;
|
||||
if (base.blankLine) baseToken = base.blankLine(state.base);
|
||||
if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
|
||||
|
||||
return overlayToken == null ?
|
||||
baseToken :
|
||||
(combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -77,17 +77,21 @@
|
||||
curLine = pos.line;
|
||||
curLineObj = cm.getLineHandle(curLine);
|
||||
}
|
||||
if (wrapping && curLineObj.height > singleLineH)
|
||||
if ((curLineObj.widgets && curLineObj.widgets.length) ||
|
||||
(wrapping && curLineObj.height > singleLineH))
|
||||
return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
|
||||
var topY = cm.heightAtLine(curLineObj, "local");
|
||||
return topY + (top ? 0 : curLineObj.height);
|
||||
}
|
||||
|
||||
var lastLine = cm.lastLine()
|
||||
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
|
||||
var ann = anns[i];
|
||||
if (ann.to.line > lastLine) continue;
|
||||
var top = nextTop || getY(ann.from, true) * hScale;
|
||||
var bottom = getY(ann.to, false) * hScale;
|
||||
while (i < anns.length - 1) {
|
||||
if (anns[i + 1].to.line > lastLine) break;
|
||||
nextTop = getY(anns[i + 1].from, true) * hScale;
|
||||
if (nextTop > bottom + .9) break;
|
||||
ann = anns[++i];
|
||||
|
@ -40,7 +40,9 @@
|
||||
if (cm.state.scrollPastEndPadding != padding) {
|
||||
cm.state.scrollPastEndPadding = padding;
|
||||
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
|
||||
cm.off("refresh", updateBottomMargin);
|
||||
cm.setSize();
|
||||
cm.on("refresh", updateBottomMargin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -59,10 +59,10 @@
|
||||
CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
|
||||
}
|
||||
|
||||
Bar.prototype.setPos = function(pos) {
|
||||
Bar.prototype.setPos = function(pos, force) {
|
||||
if (pos < 0) pos = 0;
|
||||
if (pos > this.total - this.screen) pos = this.total - this.screen;
|
||||
if (pos == this.pos) return false;
|
||||
if (!force && pos == this.pos) return false;
|
||||
this.pos = pos;
|
||||
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
|
||||
(pos * (this.size / this.total)) + "px";
|
||||
@ -76,9 +76,12 @@
|
||||
var minButtonSize = 10;
|
||||
|
||||
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
|
||||
this.screen = clientSize;
|
||||
this.total = scrollSize;
|
||||
this.size = barSize;
|
||||
var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize
|
||||
if (sizeChanged) {
|
||||
this.screen = clientSize;
|
||||
this.total = scrollSize;
|
||||
this.size = barSize;
|
||||
}
|
||||
|
||||
var buttonSize = this.screen * (this.size / this.total);
|
||||
if (buttonSize < minButtonSize) {
|
||||
@ -87,7 +90,7 @@
|
||||
}
|
||||
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
|
||||
buttonSize + "px";
|
||||
this.setPos(this.pos);
|
||||
this.setPos(this.pos, sizeChanged);
|
||||
};
|
||||
|
||||
function SimpleScrollbars(cls, place, scroll) {
|
||||
|
@ -45,6 +45,7 @@
|
||||
this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
|
||||
this.overlay = this.timeout = null;
|
||||
this.matchesonscroll = null;
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
|
||||
@ -53,16 +54,34 @@
|
||||
clearTimeout(cm.state.matchHighlighter.timeout);
|
||||
cm.state.matchHighlighter = null;
|
||||
cm.off("cursorActivity", cursorActivity);
|
||||
cm.off("focus", onFocus)
|
||||
}
|
||||
if (val) {
|
||||
cm.state.matchHighlighter = new State(val);
|
||||
highlightMatches(cm);
|
||||
var state = cm.state.matchHighlighter = new State(val);
|
||||
if (cm.hasFocus()) {
|
||||
state.active = true
|
||||
highlightMatches(cm)
|
||||
} else {
|
||||
cm.on("focus", onFocus)
|
||||
}
|
||||
cm.on("cursorActivity", cursorActivity);
|
||||
}
|
||||
});
|
||||
|
||||
function cursorActivity(cm) {
|
||||
var state = cm.state.matchHighlighter;
|
||||
if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
|
||||
}
|
||||
|
||||
function onFocus(cm) {
|
||||
var state = cm.state.matchHighlighter
|
||||
if (!state.active) {
|
||||
state.active = true
|
||||
scheduleHighlight(cm, state)
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleHighlight(cm, state) {
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
|
||||
}
|
||||
@ -72,7 +91,7 @@
|
||||
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
|
||||
if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
|
||||
var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
|
||||
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, true,
|
||||
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
|
||||
{className: "CodeMirror-selection-highlight-scrollbar"});
|
||||
}
|
||||
}
|
||||
|
@ -54,15 +54,16 @@
|
||||
|
||||
function getSearchCursor(cm, query, pos) {
|
||||
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
||||
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
|
||||
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
|
||||
}
|
||||
|
||||
function persistentDialog(cm, text, deflt, f) {
|
||||
cm.openDialog(text, f, {
|
||||
function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
|
||||
cm.openDialog(text, onEnter, {
|
||||
value: deflt,
|
||||
selectValueOnOpen: true,
|
||||
closeOnEnter: false,
|
||||
onClose: function() { clearSearch(cm); }
|
||||
onClose: function() { clearSearch(cm); },
|
||||
onKeyDown: onKeyDown
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,7 +99,7 @@
|
||||
}
|
||||
|
||||
var queryDialog =
|
||||
'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
||||
'<span class="CodeMirror-search-label">Search:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
||||
|
||||
function startSearch(cm, state, query) {
|
||||
state.queryText = query;
|
||||
@ -112,13 +113,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function doSearch(cm, rev, persistent) {
|
||||
function doSearch(cm, rev, persistent, immediate) {
|
||||
var state = getSearchState(cm);
|
||||
if (state.query) return findNext(cm, rev);
|
||||
var q = cm.getSelection() || state.lastQuery;
|
||||
if (persistent && cm.openDialog) {
|
||||
var hiding = null
|
||||
persistentDialog(cm, queryDialog, q, function(query, event) {
|
||||
var searchNext = function(query, event) {
|
||||
CodeMirror.e_stop(event);
|
||||
if (!query) return;
|
||||
if (query != state.queryText) {
|
||||
@ -133,7 +134,25 @@
|
||||
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
|
||||
(hiding = dialog).style.opacity = .4
|
||||
})
|
||||
};
|
||||
persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
|
||||
var keyName = CodeMirror.keyName(event)
|
||||
var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
|
||||
if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
|
||||
if (cmd == "findNext" || cmd == "findPrev" ||
|
||||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
|
||||
CodeMirror.e_stop(event);
|
||||
startSearch(cm, getSearchState(cm), query);
|
||||
cm.execCommand(cmd);
|
||||
} else if (cmd == "find" || cmd == "findPersistent") {
|
||||
CodeMirror.e_stop(event);
|
||||
searchNext(query, event);
|
||||
}
|
||||
});
|
||||
if (immediate && q) {
|
||||
startSearch(cm, state, q);
|
||||
findNext(cm, rev);
|
||||
}
|
||||
} else {
|
||||
dialog(cm, queryDialog, "Search for:", q, function(query) {
|
||||
if (query && !state.query) cm.operation(function() {
|
||||
@ -169,8 +188,8 @@
|
||||
|
||||
var replaceQueryDialog =
|
||||
' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
|
||||
var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
|
||||
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>";
|
||||
var replacementQueryDialog = '<span class="CodeMirror-search-label">With:</span> <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
|
||||
var doReplaceConfirm = '<span class="CodeMirror-search-label">Replace?</span> <button>Yes</button> <button>No</button> <button>All</button> <button>Stop</button>';
|
||||
|
||||
function replaceAll(cm, query, text) {
|
||||
cm.operation(function() {
|
||||
@ -186,7 +205,7 @@
|
||||
function replace(cm, all) {
|
||||
if (cm.getOption("readOnly")) return;
|
||||
var query = cm.getSelection() || getSearchState(cm).lastQuery;
|
||||
var dialogText = all ? "Replace all:" : "Replace:"
|
||||
var dialogText = '<span class="CodeMirror-search-label">' + (all ? 'Replace all:' : 'Replace:') + '</span>';
|
||||
dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
|
||||
if (!query) return;
|
||||
query = parseQuery(query);
|
||||
@ -223,6 +242,8 @@
|
||||
|
||||
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
|
||||
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
|
||||
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
|
||||
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
|
||||
CodeMirror.commands.findNext = doSearch;
|
||||
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
|
||||
CodeMirror.commands.clearSearch = clearSearch;
|
||||
|
@ -3,187 +3,279 @@
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
mod(require("../../lib/codemirror"))
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
define(["../../lib/codemirror"], mod)
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
mod(CodeMirror)
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var Pos = CodeMirror.Pos;
|
||||
"use strict"
|
||||
var Pos = CodeMirror.Pos
|
||||
|
||||
function SearchCursor(doc, query, pos, caseFold) {
|
||||
this.atOccurrence = false; this.doc = doc;
|
||||
if (caseFold == null && typeof query == "string") caseFold = false;
|
||||
function regexpFlags(regexp) {
|
||||
var flags = regexp.flags
|
||||
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
|
||||
+ (regexp.global ? "g" : "")
|
||||
+ (regexp.multiline ? "m" : "")
|
||||
}
|
||||
|
||||
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
|
||||
this.pos = {from: pos, to: pos};
|
||||
function ensureGlobal(regexp) {
|
||||
return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
|
||||
}
|
||||
|
||||
// The matches method is filled in based on the type of query.
|
||||
// It takes a position and a direction, and returns an object
|
||||
// describing the next occurrence of the query, or null if no
|
||||
// more matches were found.
|
||||
if (typeof query != "string") { // Regexp match
|
||||
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
|
||||
this.matches = function(reverse, pos) {
|
||||
if (reverse) {
|
||||
query.lastIndex = 0;
|
||||
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
|
||||
for (;;) {
|
||||
query.lastIndex = cutOff;
|
||||
var newMatch = query.exec(line);
|
||||
if (!newMatch) break;
|
||||
match = newMatch;
|
||||
start = match.index;
|
||||
cutOff = match.index + (match[0].length || 1);
|
||||
if (cutOff == line.length) break;
|
||||
}
|
||||
var matchLen = (match && match[0].length) || 0;
|
||||
if (!matchLen) {
|
||||
if (start == 0 && line.length == 0) {match = undefined;}
|
||||
else if (start != doc.getLine(pos.line).length) {
|
||||
matchLen++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query.lastIndex = pos.ch;
|
||||
var line = doc.getLine(pos.line), match = query.exec(line);
|
||||
var matchLen = (match && match[0].length) || 0;
|
||||
var start = match && match.index;
|
||||
if (start + matchLen != line.length && !matchLen) matchLen = 1;
|
||||
}
|
||||
if (match && matchLen)
|
||||
return {from: Pos(pos.line, start),
|
||||
to: Pos(pos.line, start + matchLen),
|
||||
match: match};
|
||||
};
|
||||
} else { // String query
|
||||
var origQuery = query;
|
||||
if (caseFold) query = query.toLowerCase();
|
||||
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
|
||||
var target = query.split("\n");
|
||||
// Different methods for single-line and multi-line queries
|
||||
if (target.length == 1) {
|
||||
if (!query.length) {
|
||||
// Empty string would match anything and never progress, so
|
||||
// we define it to match nothing instead.
|
||||
this.matches = function() {};
|
||||
} else {
|
||||
this.matches = function(reverse, pos) {
|
||||
if (reverse) {
|
||||
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
|
||||
var match = line.lastIndexOf(query);
|
||||
if (match > -1) {
|
||||
match = adjustPos(orig, line, match);
|
||||
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
|
||||
}
|
||||
} else {
|
||||
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
|
||||
var match = line.indexOf(query);
|
||||
if (match > -1) {
|
||||
match = adjustPos(orig, line, match) + pos.ch;
|
||||
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
var origTarget = origQuery.split("\n");
|
||||
this.matches = function(reverse, pos) {
|
||||
var last = target.length - 1;
|
||||
if (reverse) {
|
||||
if (pos.line - (target.length - 1) < doc.firstLine()) return;
|
||||
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
|
||||
var to = Pos(pos.line, origTarget[last].length);
|
||||
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
|
||||
if (target[i] != fold(doc.getLine(ln))) return;
|
||||
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
|
||||
if (fold(line.slice(cut)) != target[0]) return;
|
||||
return {from: Pos(ln, cut), to: to};
|
||||
} else {
|
||||
if (pos.line + (target.length - 1) > doc.lastLine()) return;
|
||||
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
|
||||
if (fold(line.slice(cut)) != target[0]) return;
|
||||
var from = Pos(pos.line, cut);
|
||||
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
|
||||
if (target[i] != fold(doc.getLine(ln))) return;
|
||||
if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
|
||||
return {from: from, to: Pos(ln, origTarget[last].length)};
|
||||
}
|
||||
};
|
||||
function maybeMultiline(regexp) {
|
||||
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
|
||||
}
|
||||
|
||||
function searchRegexpForward(doc, regexp, start) {
|
||||
regexp = ensureGlobal(regexp)
|
||||
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
|
||||
regexp.lastIndex = ch
|
||||
var string = doc.getLine(line), match = regexp.exec(string)
|
||||
if (match)
|
||||
return {from: Pos(line, match.index),
|
||||
to: Pos(line, match.index + match[0].length),
|
||||
match: match}
|
||||
}
|
||||
}
|
||||
|
||||
function searchRegexpForwardMultiline(doc, regexp, start) {
|
||||
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
|
||||
|
||||
regexp = ensureGlobal(regexp)
|
||||
var string, chunk = 1
|
||||
for (var line = start.line, last = doc.lastLine(); line <= last;) {
|
||||
// This grows the search buffer in exponentially-sized chunks
|
||||
// between matches, so that nearby matches are fast and don't
|
||||
// require concatenating the whole document (in case we're
|
||||
// searching for something that has tons of matches), but at the
|
||||
// same time, the amount of retries is limited.
|
||||
for (var i = 0; i < chunk; i++) {
|
||||
var curLine = doc.getLine(line++)
|
||||
string = string == null ? curLine : string + "\n" + curLine
|
||||
}
|
||||
chunk = chunk * 2
|
||||
regexp.lastIndex = start.ch
|
||||
var match = regexp.exec(string)
|
||||
if (match) {
|
||||
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
|
||||
return {from: Pos(startLine, startCh),
|
||||
to: Pos(startLine + inside.length - 1,
|
||||
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||
match: match}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchCursor.prototype = {
|
||||
findNext: function() {return this.find(false);},
|
||||
findPrevious: function() {return this.find(true);},
|
||||
|
||||
find: function(reverse) {
|
||||
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
|
||||
function savePosAndFail(line) {
|
||||
var pos = Pos(line, 0);
|
||||
self.pos = {from: pos, to: pos};
|
||||
self.atOccurrence = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (this.pos = this.matches(reverse, pos)) {
|
||||
this.atOccurrence = true;
|
||||
return this.pos.match || true;
|
||||
}
|
||||
if (reverse) {
|
||||
if (!pos.line) return savePosAndFail(0);
|
||||
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
|
||||
}
|
||||
else {
|
||||
var maxLine = this.doc.lineCount();
|
||||
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
|
||||
pos = Pos(pos.line + 1, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
from: function() {if (this.atOccurrence) return this.pos.from;},
|
||||
to: function() {if (this.atOccurrence) return this.pos.to;},
|
||||
|
||||
replace: function(newText, origin) {
|
||||
if (!this.atOccurrence) return;
|
||||
var lines = CodeMirror.splitLines(newText);
|
||||
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
|
||||
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
||||
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
|
||||
function lastMatchIn(string, regexp) {
|
||||
var cutOff = 0, match
|
||||
for (;;) {
|
||||
regexp.lastIndex = cutOff
|
||||
var newMatch = regexp.exec(string)
|
||||
if (!newMatch) return match
|
||||
match = newMatch
|
||||
cutOff = match.index + (match[0].length || 1)
|
||||
if (cutOff == string.length) return match
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function searchRegexpBackward(doc, regexp, start) {
|
||||
regexp = ensureGlobal(regexp)
|
||||
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
|
||||
var string = doc.getLine(line)
|
||||
if (ch > -1) string = string.slice(0, ch)
|
||||
var match = lastMatchIn(string, regexp)
|
||||
if (match)
|
||||
return {from: Pos(line, match.index),
|
||||
to: Pos(line, match.index + match[0].length),
|
||||
match: match}
|
||||
}
|
||||
}
|
||||
|
||||
function searchRegexpBackwardMultiline(doc, regexp, start) {
|
||||
regexp = ensureGlobal(regexp)
|
||||
var string, chunk = 1
|
||||
for (var line = start.line, first = doc.firstLine(); line >= first;) {
|
||||
for (var i = 0; i < chunk; i++) {
|
||||
var curLine = doc.getLine(line--)
|
||||
string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
|
||||
}
|
||||
chunk *= 2
|
||||
|
||||
var match = lastMatchIn(string, regexp)
|
||||
if (match) {
|
||||
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||
var startLine = line + before.length, startCh = before[before.length - 1].length
|
||||
return {from: Pos(startLine, startCh),
|
||||
to: Pos(startLine + inside.length - 1,
|
||||
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||
match: match}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doFold(str) { return str.toLowerCase() }
|
||||
function noFold(str) { return str }
|
||||
|
||||
// Maps a position in a case-folded line back to a position in the original line
|
||||
// (compensating for codepoints increasing in number during folding)
|
||||
function adjustPos(orig, folded, pos) {
|
||||
if (orig.length == folded.length) return pos;
|
||||
if (orig.length == folded.length) return pos
|
||||
for (var pos1 = Math.min(pos, orig.length);;) {
|
||||
var len1 = orig.slice(0, pos1).toLowerCase().length;
|
||||
if (len1 < pos) ++pos1;
|
||||
else if (len1 > pos) --pos1;
|
||||
else return pos1;
|
||||
var len1 = orig.slice(0, pos1).toLowerCase().length
|
||||
if (len1 < pos) ++pos1
|
||||
else if (len1 > pos) --pos1
|
||||
else return pos1
|
||||
}
|
||||
}
|
||||
|
||||
function searchStringForward(doc, query, start, caseFold) {
|
||||
// Empty string would match anything and never progress, so we
|
||||
// define it to match nothing instead.
|
||||
if (!query.length) return null
|
||||
var fold = caseFold ? doFold : noFold
|
||||
var lines = fold(query).split(/\r|\n\r?/)
|
||||
|
||||
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
|
||||
var orig = doc.getLine(line).slice(ch), string = fold(orig)
|
||||
if (lines.length == 1) {
|
||||
var found = string.indexOf(lines[0])
|
||||
if (found == -1) continue search
|
||||
var start = adjustPos(orig, string, found) + ch
|
||||
return {from: Pos(line, adjustPos(orig, string, found) + ch),
|
||||
to: Pos(line, adjustPos(orig, string, found + lines[0].length) + ch)}
|
||||
} else {
|
||||
var cutFrom = string.length - lines[0].length
|
||||
if (string.slice(cutFrom) != lines[0]) continue search
|
||||
for (var i = 1; i < lines.length - 1; i++)
|
||||
if (fold(doc.getLine(line + i)) != lines[i]) continue search
|
||||
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
|
||||
if (end.slice(0, lastLine.length) != lastLine) continue search
|
||||
return {from: Pos(line, adjustPos(orig, string, cutFrom) + ch),
|
||||
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length))}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function searchStringBackward(doc, query, start, caseFold) {
|
||||
if (!query.length) return null
|
||||
var fold = caseFold ? doFold : noFold
|
||||
var lines = fold(query).split(/\r|\n\r?/)
|
||||
|
||||
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
|
||||
var orig = doc.getLine(line)
|
||||
if (ch > -1) orig = orig.slice(0, ch)
|
||||
var string = fold(orig)
|
||||
if (lines.length == 1) {
|
||||
var found = string.lastIndexOf(lines[0])
|
||||
if (found == -1) continue search
|
||||
return {from: Pos(line, adjustPos(orig, string, found)),
|
||||
to: Pos(line, adjustPos(orig, string, found + lines[0].length))}
|
||||
} else {
|
||||
var lastLine = lines[lines.length - 1]
|
||||
if (string.slice(0, lastLine.length) != lastLine) continue search
|
||||
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
|
||||
if (fold(doc.getLine(start + i)) != lines[i]) continue search
|
||||
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
|
||||
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
|
||||
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length)),
|
||||
to: Pos(line, adjustPos(orig, string, lastLine.length))}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function SearchCursor(doc, query, pos, options) {
|
||||
this.atOccurrence = false
|
||||
this.doc = doc
|
||||
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
|
||||
this.pos = {from: pos, to: pos}
|
||||
|
||||
var caseFold
|
||||
if (typeof options == "object") {
|
||||
caseFold = options.caseFold
|
||||
} else { // Backwards compat for when caseFold was the 4th argument
|
||||
caseFold = options
|
||||
options = null
|
||||
}
|
||||
|
||||
if (typeof query == "string") {
|
||||
if (caseFold == null) caseFold = false
|
||||
this.matches = function(reverse, pos) {
|
||||
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
|
||||
}
|
||||
} else {
|
||||
query = ensureGlobal(query)
|
||||
if (!options || options.multiline !== false)
|
||||
this.matches = function(reverse, pos) {
|
||||
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
|
||||
}
|
||||
else
|
||||
this.matches = function(reverse, pos) {
|
||||
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchCursor.prototype = {
|
||||
findNext: function() {return this.find(false)},
|
||||
findPrevious: function() {return this.find(true)},
|
||||
|
||||
find: function(reverse) {
|
||||
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
|
||||
|
||||
// Implements weird auto-growing behavior on null-matches for
|
||||
// backwards-compatiblity with the vim code (unfortunately)
|
||||
while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
|
||||
if (reverse) {
|
||||
if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
|
||||
else if (result.from.line == this.doc.firstLine()) result = null
|
||||
else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
|
||||
} else {
|
||||
if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
|
||||
else if (result.to.line == this.doc.lastLine()) result = null
|
||||
else result = this.matches(reverse, Pos(result.to.line + 1, 0))
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this.pos = result
|
||||
this.atOccurrence = true
|
||||
return this.pos.match || true
|
||||
} else {
|
||||
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
|
||||
this.pos = {from: end, to: end}
|
||||
return this.atOccurrence = false
|
||||
}
|
||||
},
|
||||
|
||||
from: function() {if (this.atOccurrence) return this.pos.from},
|
||||
to: function() {if (this.atOccurrence) return this.pos.to},
|
||||
|
||||
replace: function(newText, origin) {
|
||||
if (!this.atOccurrence) return
|
||||
var lines = CodeMirror.splitLines(newText)
|
||||
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
|
||||
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
||||
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||
return new SearchCursor(this.doc, query, pos, caseFold);
|
||||
});
|
||||
return new SearchCursor(this.doc, query, pos, caseFold)
|
||||
})
|
||||
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||
return new SearchCursor(this, query, pos, caseFold);
|
||||
});
|
||||
return new SearchCursor(this, query, pos, caseFold)
|
||||
})
|
||||
|
||||
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
|
||||
var ranges = [];
|
||||
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
|
||||
var ranges = []
|
||||
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
|
||||
while (cur.findNext()) {
|
||||
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
|
||||
ranges.push({anchor: cur.from(), head: cur.to()});
|
||||
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
|
||||
ranges.push({anchor: cur.from(), head: cur.to()})
|
||||
}
|
||||
if (ranges.length)
|
||||
this.setSelections(ranges, 0);
|
||||
});
|
||||
this.setSelections(ranges, 0)
|
||||
})
|
||||
});
|
||||
|
@ -1,12 +1,6 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Because sometimes you need to style the cursor's line.
|
||||
//
|
||||
// Adds an option 'styleActiveLine' which, when enabled, gives the
|
||||
// active line's wrapping <div> the CSS class "CodeMirror-activeline",
|
||||
// and gives its background <div> the class "CodeMirror-activeline-background".
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
@ -21,16 +15,18 @@
|
||||
var GUTT_CLASS = "CodeMirror-activeline-gutter";
|
||||
|
||||
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
|
||||
var prev = old && old != CodeMirror.Init;
|
||||
if (val && !prev) {
|
||||
cm.state.activeLines = [];
|
||||
updateActiveLines(cm, cm.listSelections());
|
||||
cm.on("beforeSelectionChange", selectionChange);
|
||||
} else if (!val && prev) {
|
||||
var prev = old == CodeMirror.Init ? false : old;
|
||||
if (val == prev) return
|
||||
if (prev) {
|
||||
cm.off("beforeSelectionChange", selectionChange);
|
||||
clearActiveLines(cm);
|
||||
delete cm.state.activeLines;
|
||||
}
|
||||
if (val) {
|
||||
cm.state.activeLines = [];
|
||||
updateActiveLines(cm, cm.listSelections());
|
||||
cm.on("beforeSelectionChange", selectionChange);
|
||||
}
|
||||
});
|
||||
|
||||
function clearActiveLines(cm) {
|
||||
@ -52,7 +48,9 @@
|
||||
var active = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i];
|
||||
if (!range.empty()) continue;
|
||||
var option = cm.getOption("styleActiveLine");
|
||||
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
|
||||
continue
|
||||
var line = cm.getLineHandleVisualStart(range.head.line);
|
||||
if (active[active.length - 1] != line) active.push(line);
|
||||
}
|
||||
|
@ -34,11 +34,12 @@
|
||||
});
|
||||
|
||||
function onCursorActivity(cm) {
|
||||
cm.operation(function() { update(cm); });
|
||||
if (cm.state.markedSelection)
|
||||
cm.operation(function() { update(cm); });
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
if (cm.state.markedSelection.length)
|
||||
if (cm.state.markedSelection && cm.state.markedSelection.length)
|
||||
cm.operation(function() { clear(cm); });
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,9 @@
|
||||
}
|
||||
|
||||
function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
|
||||
for (var at = column; at > 0; --at)
|
||||
var at = column
|
||||
while (at < text.length && text.charAt(at) == " ") at++
|
||||
for (; at > 0; --at)
|
||||
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
|
||||
for (var first = true;; first = false) {
|
||||
var endOfText = at;
|
||||
|
@ -52,7 +52,7 @@
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
@ -88,8 +88,14 @@
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@ -113,7 +119,7 @@
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
@ -200,9 +206,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
@ -220,11 +223,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
@ -246,8 +246,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: none;
|
||||
font-variant-ligatures: none;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
@ -269,6 +269,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
@ -291,7 +293,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor { position: absolute; }
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
@ -318,9 +323,6 @@ div.CodeMirror-dragcursors {
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
|
17087
web/pgadmin/static/vendor/codemirror/lib/codemirror.js
vendored
17087
web/pgadmin/static/vendor/codemirror/lib/codemirror.js
vendored
File diff suppressed because it is too large
Load Diff
@ -32,13 +32,13 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
if (result !== false) return result;
|
||||
}
|
||||
|
||||
if (support.hexNumber == true &&
|
||||
if (support.hexNumber &&
|
||||
((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/))
|
||||
|| (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) {
|
||||
// hex
|
||||
// ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html
|
||||
return "number";
|
||||
} else if (support.binaryNumber == true &&
|
||||
} else if (support.binaryNumber &&
|
||||
(((ch == "b" || ch == "B") && stream.match(/^'[01]+'/))
|
||||
|| (ch == "0" && stream.match(/^b[01]+/)))) {
|
||||
// bitstring
|
||||
@ -48,7 +48,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
// numbers
|
||||
// ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html
|
||||
stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/);
|
||||
support.decimallessFloat == true && stream.eat('.');
|
||||
support.decimallessFloat && stream.eat('.');
|
||||
return "number";
|
||||
} else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) {
|
||||
// placeholders
|
||||
@ -58,8 +58,8 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
// ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
|
||||
state.tokenize = tokenLiteral(ch);
|
||||
return state.tokenize(stream, state);
|
||||
} else if ((((support.nCharCast == true && (ch == "n" || ch == "N"))
|
||||
|| (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i)))
|
||||
} else if ((((support.nCharCast && (ch == "n" || ch == "N"))
|
||||
|| (support.charsetCast && ch == "_" && stream.match(/[a-z][a-z0-9]*/i)))
|
||||
&& (stream.peek() == "'" || stream.peek() == '"'))) {
|
||||
// charset casting: _utf8'str', N'str', n'str'
|
||||
// ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
|
||||
@ -80,16 +80,16 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
} else if (ch == "/" && stream.eat("*")) {
|
||||
// multi-line comments
|
||||
// ref: https://kb.askmonty.org/en/comment-syntax/
|
||||
state.tokenize = tokenComment;
|
||||
state.tokenize = tokenComment(1);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == ".") {
|
||||
// .1 for 0.1
|
||||
if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) {
|
||||
if (support.zerolessFloat && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) {
|
||||
return "number";
|
||||
}
|
||||
// .table_name (ODBC)
|
||||
// // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
|
||||
if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) {
|
||||
if (support.ODBCdotTable && stream.match(/^[a-zA-Z_]+/)) {
|
||||
return "variable-2";
|
||||
}
|
||||
} else if (operatorChars.test(ch)) {
|
||||
@ -130,20 +130,15 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
return "string";
|
||||
};
|
||||
}
|
||||
function tokenComment(stream, state) {
|
||||
while (true) {
|
||||
if (stream.skipTo("*")) {
|
||||
stream.next();
|
||||
if (stream.eat("/")) {
|
||||
state.tokenize = tokenBase;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
break;
|
||||
}
|
||||
function tokenComment(depth) {
|
||||
return function(stream, state) {
|
||||
var m = stream.match(/^.*?(\/\*|\*\/)/)
|
||||
if (!m) stream.skipToEnd()
|
||||
else if (m[1] == "/*") state.tokenize = tokenComment(depth + 1)
|
||||
else if (depth > 1) state.tokenize = tokenComment(depth - 1)
|
||||
else state.tokenize = tokenBase
|
||||
return "comment"
|
||||
}
|
||||
return "comment";
|
||||
}
|
||||
|
||||
function pushContext(stream, state, type) {
|
||||
@ -170,7 +165,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
if (state.context && state.context.align == null)
|
||||
state.context.align = false;
|
||||
}
|
||||
if (stream.eatSpace()) return null;
|
||||
if (state.tokenize == tokenBase && stream.eatSpace()) return null;
|
||||
|
||||
var style = state.tokenize(stream, state);
|
||||
if (style == "comment") return style;
|
||||
@ -217,6 +212,19 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
return stream.eatWhile(/\w/) ? "variable-2" : null;
|
||||
}
|
||||
|
||||
// "identifier"
|
||||
function hookIdentifierDoublequote(stream) {
|
||||
// Standard SQL /SQLite identifiers
|
||||
// ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier
|
||||
// ref: http://sqlite.org/lang_keywords.html
|
||||
var ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == "\"" && !stream.eat("\"")) return "variable-2";
|
||||
}
|
||||
stream.backUp(stream.current().length - 1);
|
||||
return stream.eatWhile(/\w/) ? "variable-2" : null;
|
||||
}
|
||||
|
||||
// variable token
|
||||
function hookVar(stream) {
|
||||
// variables
|
||||
@ -280,7 +288,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
CodeMirror.defineMIME("text/x-mssql", {
|
||||
name: "sql",
|
||||
client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
|
||||
keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare"),
|
||||
keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec"),
|
||||
builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "),
|
||||
atoms: set("false true null unknown"),
|
||||
operatorChars: /^[*+\-%<>!=]/,
|
||||
@ -322,6 +330,36 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
}
|
||||
});
|
||||
|
||||
// provided by the phpLiteAdmin project - phpliteadmin.org
|
||||
CodeMirror.defineMIME("text/x-sqlite", {
|
||||
name: "sql",
|
||||
// commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd
|
||||
client: set("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"),
|
||||
// ref: http://sqlite.org/lang_keywords.html
|
||||
keywords: set(sqlKeywords + "abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"),
|
||||
// SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.
|
||||
builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"),
|
||||
// ref: http://sqlite.org/syntax/literal-value.html
|
||||
atoms: set("null current_date current_time current_timestamp"),
|
||||
// ref: http://sqlite.org/lang_expr.html#binaryops
|
||||
operatorChars: /^[*+\-%<>!=&|/~]/,
|
||||
// SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.
|
||||
dateSQL: set("date time timestamp datetime"),
|
||||
support: set("decimallessFloat zerolessFloat"),
|
||||
identifierQuote: "\"", //ref: http://sqlite.org/lang_keywords.html
|
||||
hooks: {
|
||||
// bind-parameters ref:http://sqlite.org/lang_expr.html#varparam
|
||||
"@": hookVar,
|
||||
":": hookVar,
|
||||
"?": hookVar,
|
||||
"$": hookVar,
|
||||
// The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html
|
||||
"\"": hookIdentifierDoublequote,
|
||||
// there is also support for backtics, ref: http://sqlite.org/lang_keywords.html
|
||||
"`": hookIdentifier
|
||||
}
|
||||
});
|
||||
|
||||
// the query language used by Apache Cassandra is called CQL, but this mime type
|
||||
// is called Cassandra to avoid confusion with Contextual Query Language
|
||||
CodeMirror.defineMIME("text/x-cassandra", {
|
||||
@ -366,9 +404,9 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
|
||||
// http://www.postgresql.org/docs/9.5/static/datatype.html
|
||||
builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"),
|
||||
atoms: set("false true null unknown"),
|
||||
operatorChars: /^[*+\-%<>!=&|^]/,
|
||||
operatorChars: /^[*+\-%<>!=&|^\/#@?~]/,
|
||||
dateSQL: set("date time timestamp"),
|
||||
support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast commentHash commentSpaceRequired")
|
||||
support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast")
|
||||
});
|
||||
|
||||
// Google's SQL-like query language, GQL
|
||||
|
@ -59,6 +59,7 @@
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/codemirror.overrides.css') }}"/>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.css') }}"/>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/pgadmin.style.css') }}"/>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/codemirror.overrides.css') }}"/>
|
||||
{% block css_link %}{% endblock %}
|
||||
|
||||
<!-- Base template scripts -->
|
||||
|
@ -973,14 +973,9 @@ define([
|
||||
render_history_grid: function() {
|
||||
var self = this;
|
||||
|
||||
// Remove any existing grid first
|
||||
if (self.history_grid) {
|
||||
self.history_grid.remove();
|
||||
}
|
||||
self.history_collection = new HistoryBundle.HistoryCollection([]);
|
||||
|
||||
self.history_collection = new HistoryBundle.historyCollection([]);
|
||||
|
||||
let queryHistoryElement = reactComponents.React.createElement(
|
||||
var queryHistoryElement = reactComponents.React.createElement(
|
||||
reactComponents.QueryHistory, {historyCollection: self.history_collection});
|
||||
reactComponents.render(queryHistoryElement, $('#history_grid')[0]);
|
||||
},
|
||||
@ -1771,6 +1766,7 @@ define([
|
||||
},
|
||||
error: function(e) {
|
||||
// Enable/Disable query tool button only if is_query_tool is true.
|
||||
self.resetQueryHistoryObject(self);
|
||||
self.trigger('pgadmin-sqleditor:loading-icon:hide');
|
||||
if (self.is_query_tool) {
|
||||
self.disable_tool_buttons(false);
|
||||
@ -1856,7 +1852,6 @@ define([
|
||||
|
||||
// Show message in message and history tab in case of query tool
|
||||
self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
|
||||
self.update_msg_history(true, "", false);
|
||||
var msg1 = S(gettext("Total query runtime: %s.")).sprintf(self.total_time).value();
|
||||
var msg2 = S(gettext("%s rows affected.")).sprintf(self.rows_affected).value();
|
||||
|
||||
@ -1867,6 +1862,7 @@ define([
|
||||
|
||||
var _msg = msg1 + '\n' + msg2;
|
||||
|
||||
self.update_msg_history(true, _msg, false);
|
||||
// If there is additional messages from server then add it to message
|
||||
if(!_.isNull(data.additional_messages) &&
|
||||
!_.isUndefined(data.additional_messages)) {
|
||||
@ -2030,6 +2026,10 @@ define([
|
||||
}
|
||||
},
|
||||
|
||||
resetQueryHistoryObject: function (history) {
|
||||
history.total_time = '-';
|
||||
},
|
||||
|
||||
// This function is used to raise appropriate message.
|
||||
update_msg_history: function(status, msg, clear_grid) {
|
||||
var self = this;
|
||||
|
@ -41,7 +41,6 @@ class AppStarter:
|
||||
)
|
||||
|
||||
self.driver.set_window_size(1024, 1024)
|
||||
time.sleep(10)
|
||||
self.driver.get(
|
||||
"http://" + self.app_config.DEFAULT_SERVER + ":" +
|
||||
random_server_port)
|
||||
|
@ -27,7 +27,7 @@ class PgadminPage:
|
||||
def __init__(self, driver, app_config):
|
||||
self.driver = driver
|
||||
self.app_config = app_config
|
||||
self.timeout = 20
|
||||
self.timeout = 30
|
||||
self.app_start_timeout = 60
|
||||
|
||||
def reset_layout(self):
|
||||
|
68
web/regression/javascript/code_mirror_spec.jsx
Normal file
68
web/regression/javascript/code_mirror_spec.jsx
Normal file
@ -0,0 +1,68 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import $ from '../../pgadmin/static/vendor/jquery/jquery-1.11.2';
|
||||
import CodeMirror from '../../pgadmin/static/jsx/history/detail/code_mirror';
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
|
||||
describe('CodeMirror', () => {
|
||||
beforeEach(() => {
|
||||
jasmineEnzyme();
|
||||
});
|
||||
|
||||
describe('#hydrateWhenBecomesVisible', () => {
|
||||
let codeMirror, isVisibleSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
codeMirror = shallow(<CodeMirror />).instance();
|
||||
isVisibleSpy = spyOn($.fn, 'is');
|
||||
spyOn(codeMirror, 'hydrate');
|
||||
});
|
||||
|
||||
describe('when component is visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(true);
|
||||
});
|
||||
|
||||
it('should hydrate the codemirror element', () => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when component is not visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(false);
|
||||
});
|
||||
|
||||
it('should not hydrate the codemirror element', () => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when becomes visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(true);
|
||||
});
|
||||
|
||||
it('should hydrate the codemirror element', (done) => {
|
||||
setTimeout(() => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -66,10 +66,6 @@ describe('historyCollection', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sort', function () {
|
||||
it('doesn\'t sort');
|
||||
});
|
||||
|
||||
describe('when instantiated', function () {
|
||||
describe('from a history model', function () {
|
||||
it('has the historyModel', () => {
|
||||
|
@ -1,50 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
|
||||
describe('QueryHistoryEntry', () => {
|
||||
let historyWrapper;
|
||||
beforeEach(() => {
|
||||
jasmineEnzyme();
|
||||
});
|
||||
|
||||
describe('for a failed query', () => {
|
||||
beforeEach(() => {
|
||||
const historyEntry = {
|
||||
query: 'second sql statement',
|
||||
start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
|
||||
status: false,
|
||||
};
|
||||
historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
|
||||
});
|
||||
it('displays a pink background color', () => {
|
||||
expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a successful query', () => {
|
||||
beforeEach(() => {
|
||||
const historyEntry = {
|
||||
query: 'second sql statement',
|
||||
start_time: new Date(2016, 11, 11, 1, 33, 5, 99),
|
||||
status: true,
|
||||
};
|
||||
historyWrapper = mount(<QueryHistoryEntry historyEntry={historyEntry}/>);
|
||||
});
|
||||
it('does not display a pink background color', () => {
|
||||
expect(historyWrapper.find('div').first()).toHaveStyle('backgroundColor', '#FFF');
|
||||
});
|
||||
});
|
||||
});
|
@ -10,30 +10,45 @@
|
||||
import React from 'react';
|
||||
import QueryHistory from '../../../pgadmin/static/jsx/history/query_history';
|
||||
import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
|
||||
import QueryHistoryDetail from '../../../pgadmin/static/jsx/history/query_history_detail';
|
||||
import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
|
||||
import {mount, shallow} from 'enzyme';
|
||||
import {mount} from 'enzyme';
|
||||
|
||||
describe('QueryHistory', () => {
|
||||
let historyWrapper;
|
||||
beforeEach(() => {
|
||||
jasmineEnzyme();
|
||||
const historyCollection = new HistoryCollection([]);
|
||||
historyWrapper = shallow(<QueryHistory historyCollection={historyCollection}/>);
|
||||
});
|
||||
|
||||
describe('on construction', () => {
|
||||
describe('on construction, when there is no history', () => {
|
||||
beforeEach(function () {
|
||||
const historyCollection = new HistoryCollection([]);
|
||||
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>);
|
||||
});
|
||||
|
||||
it('has no entries', (done) => {
|
||||
let foundChildren = historyWrapper.find(QueryHistoryEntry);
|
||||
expect(foundChildren.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
|
||||
it('nothing is displayed on right panel', (done) => {
|
||||
let foundChildren = historyWrapper.find(QueryHistoryDetail);
|
||||
expect(foundChildren.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('does not error', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it has history', () => {
|
||||
describe('when two SQL queries were executed', () => {
|
||||
let foundChildren;
|
||||
let queryDetail;
|
||||
let historyCollection;
|
||||
|
||||
beforeEach(() => {
|
||||
const historyObjects = [
|
||||
@ -43,59 +58,114 @@ describe('QueryHistory', () => {
|
||||
status: false,
|
||||
row_affected: 1,
|
||||
total_time: '234 msec',
|
||||
message: 'some other message',
|
||||
message: 'message from second sql query',
|
||||
},
|
||||
{
|
||||
query: 'first sql statement',
|
||||
start_time: new Date(2017, 5, 3, 14, 3, 15, 150),
|
||||
status: true,
|
||||
row_affected: 2,
|
||||
row_affected: 12345,
|
||||
total_time: '14 msec',
|
||||
message: 'a very important message',
|
||||
message: 'message from first sql query',
|
||||
},
|
||||
];
|
||||
const historyCollection = new HistoryCollection(historyObjects);
|
||||
historyCollection = new HistoryCollection(historyObjects);
|
||||
|
||||
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}/>);
|
||||
|
||||
foundChildren = historyWrapper.find(QueryHistoryEntry);
|
||||
queryDetail = historyWrapper.find(QueryHistoryDetail);
|
||||
});
|
||||
|
||||
it('has two query history entries', () => {
|
||||
expect(foundChildren.length).toBe(2);
|
||||
});
|
||||
|
||||
it('displays the SQL of the queries in order', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('first sql statement');
|
||||
expect(foundChildren.at(1).text()).toContain('second sql statement');
|
||||
});
|
||||
|
||||
it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('Jun 3 2017 – 14:03:15');
|
||||
expect(foundChildren.at(1).text()).toContain('Dec 11 2016 – 01:33:05');
|
||||
});
|
||||
|
||||
it('displays the number of rows affected', () => {
|
||||
expect(foundChildren.at(1).text()).toContain('1 rows affected');
|
||||
expect(foundChildren.at(0).text()).toContain('2 rows affected');
|
||||
});
|
||||
|
||||
it('displays the total time', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('total time: 14 msec');
|
||||
expect(foundChildren.at(1).text()).toContain('total time: 234 msec');
|
||||
});
|
||||
|
||||
it('displays the truncated message', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('a very important message');
|
||||
expect(foundChildren.at(1).text()).toContain('some other message');
|
||||
});
|
||||
|
||||
describe('when there are one failing and one successful query each', () => {
|
||||
it('adds a white background color for the successful query', () => {
|
||||
expect(foundChildren.at(0).find('div').first()).toHaveStyle('backgroundColor', '#FFF');
|
||||
describe('the main pane', () => {
|
||||
it('has two query history entries', () => {
|
||||
expect(foundChildren.length).toBe(2);
|
||||
});
|
||||
it('adds a red background color for the failed query', () => {
|
||||
expect(foundChildren.at(1).find('div').first()).toHaveStyle('backgroundColor', '#F7D0D5');
|
||||
|
||||
it('displays the query history entries in order', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('first sql statement');
|
||||
expect(foundChildren.at(1).text()).toContain('second sql statement');
|
||||
});
|
||||
|
||||
it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
|
||||
expect(foundChildren.at(0).text()).toContain('Jun 3 2017 – 14:03:15');
|
||||
expect(foundChildren.at(1).text()).toContain('Dec 11 2016 – 01:33:05');
|
||||
});
|
||||
|
||||
it('renders the most recent query as selected', () => {
|
||||
expect(foundChildren.at(0).nodes.length).toBe(1);
|
||||
expect(foundChildren.at(0).find('QueryHistorySelectedEntry').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders the older query as not selected', () => {
|
||||
expect(foundChildren.at(1).nodes.length).toBe(1);
|
||||
expect(foundChildren.at(1).find('QueryHistoryErrorEntry').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the details pane', () => {
|
||||
it('displays the formatted timestamp', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('6-3-17 14:03:15Date');
|
||||
});
|
||||
|
||||
it('displays the number of rows affected', () => {
|
||||
if (/PhantomJS/.test(window.navigator.userAgent)) {
|
||||
expect(queryDetail.at(0).text()).toContain('12345Rows Affected');
|
||||
} else {
|
||||
expect(queryDetail.at(0).text()).toContain('12,345Rows Affected');
|
||||
}
|
||||
});
|
||||
|
||||
it('displays the total time', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('14 msecDuration');
|
||||
});
|
||||
|
||||
it('displays the full message', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('message from first sql query');
|
||||
});
|
||||
|
||||
it('displays first query SQL', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(queryDetail.at(0).text()).toContain('first sql statement');
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the older query is clicked on', () => {
|
||||
beforeEach(() => {
|
||||
foundChildren.at(1).simulate('click');
|
||||
});
|
||||
|
||||
it('displays the query in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('second sql statement');
|
||||
});
|
||||
|
||||
it('renders the most recent query as selected in the left pane', () => {
|
||||
expect(foundChildren.at(0).nodes.length).toBe(1);
|
||||
expect(foundChildren.at(0).find('QueryHistoryVanillaEntry').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders the older query as selected in the left pane', () => {
|
||||
expect(foundChildren.at(1).nodes.length).toBe(1);
|
||||
expect(foundChildren.at(1).find('QueryHistorySelectedErrorEntry').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a third SQL query is executed', () => {
|
||||
beforeEach(() => {
|
||||
historyCollection.add({
|
||||
query: 'third sql statement',
|
||||
start_time: new Date(2017, 11, 11, 1, 33, 5, 99),
|
||||
status: false,
|
||||
row_affected: 5,
|
||||
total_time: '26 msec',
|
||||
message: 'third sql message',
|
||||
});
|
||||
});
|
||||
|
||||
it('displays third query SQL in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('third sql statement');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -828,8 +828,8 @@ bluebird@^3.3.0:
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
|
||||
version "4.11.6"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
|
||||
version "4.11.7"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
|
||||
|
||||
body-parser@^1.16.1:
|
||||
version "1.17.2"
|
||||
@ -856,6 +856,10 @@ boom@2.x.x:
|
||||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
bowser@^1.6.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.7.0.tgz#169de4018711f994242bff9a8009e77a1f35e003"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
||||
@ -1400,6 +1404,12 @@ crypto-browserify@^3.0.0, crypto-browserify@^3.11.0:
|
||||
public-encrypt "^4.0.0"
|
||||
randombytes "^2.0.0"
|
||||
|
||||
css-in-js-utils@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-1.0.3.tgz#9ac7e02f763cf85d94017666565ed68a5b5f3215"
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.2"
|
||||
|
||||
css-color-names@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||
@ -2489,6 +2499,10 @@ https-browserify@0.0.1, https-browserify@~0.0.0:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
|
||||
|
||||
hyphenate-style-name@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz#31160a36930adaf1fc04c6074f7eb41465d4ec4b"
|
||||
|
||||
iconv-lite@0.4.15, iconv-lite@~0.4.13:
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
|
||||
@ -2561,6 +2575,13 @@ inline-source-map@~0.6.0:
|
||||
dependencies:
|
||||
source-map "~0.5.3"
|
||||
|
||||
inline-style-prefixer@^3.0.2:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.6.tgz#b27fe309b4168a31eaf38c8e8c60ab9e7c11731f"
|
||||
dependencies:
|
||||
bowser "^1.6.0"
|
||||
css-in-js-utils "^1.0.3"
|
||||
|
||||
inquirer@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
|
||||
@ -4043,7 +4064,7 @@ promise@^7.1.1:
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.5.4:
|
||||
prop-types@^15.5.4, prop-types@^15.5.8:
|
||||
version "15.5.10"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
|
||||
dependencies:
|
||||
@ -4155,6 +4176,20 @@ react-addons-test-utils@~15.4.2:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react-split-pane@^0.1.63:
|
||||
version "0.1.63"
|
||||
resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.63.tgz#fadb3960cc659911dd05ffbc88acee4be9f53583"
|
||||
dependencies:
|
||||
inline-style-prefixer "^3.0.2"
|
||||
prop-types "^15.5.8"
|
||||
react-style-proptype "^3.0.0"
|
||||
|
||||
react-style-proptype@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.0.0.tgz#89e0b646f266c656abb0f0dd8202dbd5036c31e6"
|
||||
dependencies:
|
||||
prop-types "^15.5.4"
|
||||
|
||||
"react@file:../web/pgadmin/static/vendor/react":
|
||||
version "15.4.2"
|
||||
dependencies:
|
||||
@ -4184,8 +4219,8 @@ read-pkg@^1.0.0:
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.6:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.0.tgz#640f5dcda88c91a8dc60787145629170813a1ed2"
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.1.tgz#84e26965bb9e785535ed256e8d38e92c69f09d10"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
|
Loading…
Reference in New Issue
Block a user